<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- Spring Boot版本 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.6</version>
</parent>
<groupId>com.manong</groupId>
<artifactId>authority-system</artifactId>
<version>1.0</version>
<properties>
<java.version>1.8</java.version>
<jwt.version>0.9.1</jwt.version>
<mybatis-plus.version>3.5.1</mybatis-plus.version>
<fastjson.version>1.2.80</fastjson.version>
</properties>
<dependencies>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- mybatis plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<!-- lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 热部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<!-- 单元测试 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- fast json -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>${jwt.version}</version>
</dependency>
<!-- redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 以下是代码生成器的jar依赖 -->
<!-- 代码生成器核心jar依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.1</version>
</dependency>
<!-- 使用默认的velocity模板 -->
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.0</version>
</dependency>
<!-- sfl4j -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</dependency>
<!-- Spring Security-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.5.5</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
#设置端口号
server.port=8888
#数据库驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/db_authority_system?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
#数据库用户名
spring.datasource.username=root
#数据库密码
spring.datasource.password=root
#加载映射文件
mybatis-plus.mapper-locations=classpath*:/mapper/**/*.xml
#设置别名
mybatis-plus.type-aliases-package=com.ftz.entity
#关闭驼峰命名映射
#mybatis-plus.configuration.map-underscore-to-camel-case=false
#显示日志
logging.level.com.manong.dao=debug
#JSON日期格式化
spring.jackson.date-format= yyyy-MM-dd
#JSON日期格式化设置时区为上海
spring.jackson.time-zone=Asia/Shanghai
#日期格式化
spring.mvc.format.date=yyyy-MM-dd
spring.mvc.format.date-time=yyyy-MM-dd HH:mm:ss
#jwt配置
#密钥
jwt.secret=com.ftz
#过期时间
jwt.expiration=1800000
package com.ftz.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import java.time.LocalDateTime;
import java.io.Serializable;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
@Data
@TableName("sys_user")
public class User implements Serializable, UserDetails {
private static final long serialVersionUID = 1L;
/**
* 用户编号
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 登录名称(用户名)
*/
private String username;
/**
* 登录密码
*/
private String password;
/**
* 帐户是否过期(1-未过期,0-已过期)
*/
private boolean isAccountNonExpired = true;
/**
* 帐户是否被锁定(1-未过期,0-已过期)
*/
private boolean isAccountNonLocked = true;
/**
* 密码是否过期(1-未过期,0-已过期)
*/
private boolean isCredentialsNonExpired = true;
/**
* 帐户是否可用(1-可用,0-禁用)
*/
private boolean isEnabled = true;
/**
* 真实姓名
*/
private String realName;
/**
* 昵称
*/
private String nickName;
/**
* 所属部门ID
*/
private Long departmentId;
/**
* 所属部门名称
*/
private String departmentName;
/**
* 性别(0-男,1-女)
*/
private Integer gender;
/**
* 电话
*/
private String phone;
/**
* 邮箱
*/
private String email;
/**
* 用户头像
*/
private String avatar;
/**
* 是否是管理员(1-管理员)
*/
private Integer isAdmin;
/**
* 创建时间
*/
private Date createTime;
/**
* 修改时间
*/
private Date updateTime;
/**
* 是否删除(0-未删除,1-已删除)
*/
private Integer isDelete;
/**
* 权限列表
*/
@TableField(exist = false)
Collection<? extends GrantedAuthority> authorities;
/**
* 查询用户权限列表
*/
@TableField(exist = false)
private List<Permission> permissionList;
}
package com.ftz.config.security.service;
import com.ftz.entity.User;
import com.ftz.service.UserService;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
/**
* 用户认证处理器类
*/
@Component
public class CustomerUserDetailsService implements UserDetailsService {
@Resource
private UserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//调用根据用户名查询用户信息的方法
User user = userService.findUserByUserName(username);
//如果对象为空,则认证失败
if (user == null) {
throw new UsernameNotFoundException("用户名或密码错误!");
}
//查询成功
return user;
}
}
package com.ftz.config.security.handler;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.ftz.entity.User;
import com.ftz.utils.JwtUtils;
import com.ftz.utils.LoginResult;
import com.ftz.utils.ResultCode;
import io.jsonwebtoken.Jwts;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 登录认证成功处理器类
*/
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
@Resource
private JwtUtils jwtUtils;
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response,
Authentication authentication) throws IOException, ServletException {
//设置客户端的响应的内容类型
response.setContentType("application/json;charset=UTF-8");
//获取当登录用户信息
User user = (User) authentication.getPrincipal();
//生成token
String token = jwtUtils.generateToken(user);
//设置token的签名密钥以及过期时间
long expireTime = Jwts.parser()
.setSigningKey(jwtUtils.getSecret()) //设置签名密钥
.parseClaimsJws(token.replace("jwt_", ""))
.getBody().getExpiration().getTime();//设置火气时间
//创建LoginResult登陆结果对象
LoginResult loginResult = new LoginResult(user.getId(), ResultCode.SUCCESS,token,expireTime);
//消除循环引用
String result = JSON.toJSONString(loginResult, SerializerFeature.DisableCircularReferenceDetect);
//获取输出流
ServletOutputStream outputStream = response.getOutputStream();
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
package com.ftz.config.security.handler;
import com.alibaba.fastjson.JSON;
import com.ftz.utils.Result;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 用户认证失败处理类
*/
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {
@Override
public void onAuthenticationFailure(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception) throws IOException, ServletException {
//设置客户端响应编码格式
response.setContentType("application/json;charset=UTF-8");
//获取输出流
ServletOutputStream outputStream = response.getOutputStream();
String message = null;//提示信息
int code = 500;//错误编码
//判断异常类型
if(exception instanceof AccountExpiredException){
message = "账户过期,登录失败!";
}else if(exception instanceof BadCredentialsException){
message = "用户名或密码错误,登录失败!";
}else if(exception instanceof CredentialsExpiredException){
message = "密码过期,登录失败!";
}else if(exception instanceof DisabledException){
message = "账户被禁用,登录失败!";
}else if(exception instanceof LockedException){
message = "账户被锁,登录失败!";
}else if(exception instanceof InternalAuthenticationServiceException){
message = "账户不存在,登录失败!";
}else{
message = "登录失败!";
}
//将错误信息转换成JSON
String result = JSON.toJSONString(Result.error().code(code).message(message));
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
package com.ftz.config.security.handler;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.ftz.utils.Result;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 匿名用户访问资源处理器
*/
@Component
public class AnonymousAuthenticationHandler implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse
response, AuthenticationException authException) throws IOException, ServletException {
//设置客户端的响应的内容类型
response.setContentType("application/json;charset=UTF-8");
//获取输出流
ServletOutputStream outputStream = response.getOutputStream();
//消除循环引用
String result = JSON.toJSONString(Result.error().code(600).message("匿名用户无权限访问!"),
SerializerFeature.DisableCircularReferenceDetect);
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
package com.ftz.config.security.handler;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.ftz.utils.Result;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* 认证用户访问无权限资源时处理器
*/
@Component
public class CustomerAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response,
AccessDeniedException accessDeniedException) throws IOException, ServletException
{
//设置客户端的响应的内容类型
response.setContentType("application/json;charset=UTF-8");
//获取输出流
ServletOutputStream outputStream = response.getOutputStream();
//消除循环引用
String result = JSON.toJSONString(Result.error().code(700).message("无权限访问,请联系管理员!"),
SerializerFeature.DisableCircularReferenceDetect);
outputStream.write(result.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
outputStream.close();
}
}
package com.ftz.config.security;
import com.ftz.config.security.handler.AnonymousAuthenticationHandler;
import com.ftz.config.security.handler.CustomerAccessDeniedHandler;
import com.ftz.config.security.handler.LoginFailureHandler;
import com.ftz.config.security.handler.LoginSuccessHandler;
import com.ftz.config.security.service.CustomerUserDetailsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import javax.annotation.Resource;
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private CustomerUserDetailsService customerUserDetailsService;
@Resource
private LoginSuccessHandler loginSuccessHandler;
@Resource
private LoginFailureHandler loginFailureHandler;
@Resource
private AnonymousAuthenticationHandler anonymousAuthenticationHandler;
@Resource
private CustomerAccessDeniedHandler customerAccessDeniedHandler;
/**
* 注入加密处理类
* @return
*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//登录前进行过滤
http.formLogin() //表单登陆
.loginProcessingUrl("/api/user/login")//登陆请求的url地址,自定义即可
// 设置登录验证成功或失败后的的跳转地址
.successHandler(loginSuccessHandler) //认证成功处理器
.failureHandler(loginFailureHandler) //认证失败处理器
// 禁用csrf防御机制
.and().csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)// 不创建session
.and()
.authorizeRequests() //设置需要拦截的请求
.antMatchers("/api/user/login").permitAll() // 登陆请求放行
.anyRequest().authenticated() //其他吐绿的请求都需要进行身份认证
.and()
.exceptionHandling()
.authenticationEntryPoint(anonymousAuthenticationHandler) //匿名无权限访问
.accessDeniedHandler(customerAccessDeniedHandler) //认证用户无权限访问
.and().cors();//开启跨域配置
}
/**
* 配置认证处理器
*
* @param auth
* @throws Exception
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception
{
auth.userDetailsService(customerUserDetailsService).passwordEncoder(passwordEncoder());
}
}
package com.ftz.utils;
import com.ftz.entity.User;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Data
@ConfigurationProperties(prefix = "jwt")
@Component
public class JwtUtils {
//密钥
private String secret;
// 过期时间 毫秒
private Long expiration;
/**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
private String generateToken(Map<String, Object> claims) {
Date expirationDate = new Date(System.currentTimeMillis() + expiration);
return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
}
/**
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
public Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
} catch (Exception e) {
claims = null;
}
return claims;
}
/**
* 生成令牌
*
* @param userDetails 用户
* @return 令牌
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>(2);
claims.put(Claims.SUBJECT, userDetails.getUsername());
claims.put(Claims.ISSUED_AT, new Date());
return generateToken(claims);
}
/**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
}
/**
* 判断令牌是否过期
*
* @param token 令牌
* @return 是否过期
*/
public Boolean isTokenExpired(String token) {
Claims claims = getClaimsFromToken(token);
Date expiration = claims.getExpiration();
return expiration.before(new Date());
}
/**
* 刷新令牌
*
* @param token 原令牌
* @return 新令牌
*/
public String refreshToken(String token) {
String refreshedToken;
try {
Claims claims = getClaimsFromToken(token);
claims.put(Claims.ISSUED_AT, new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
}
/**
* 验证令牌
*
* @param token 令牌
* @param userDetails 用户
* @return 是否有效
*/
public Boolean validateToken(String token, UserDetails userDetails) {
User user = (User) userDetails;
String username = getUsernameFromToken(token);
return (username.equals(user.getUsername()) && !isTokenExpired(token));
}
}
package com.ftz.utils;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class LoginResult {
//用户编号
private Long id;
//状态码
private int code;
//token令牌
private String token;
//token过期时间
private Long expireTime;
}
package com.ftz.utils;
import lombok.Data;
/**
* 全局统一返回结果类
*/
@Data
public class Result<T> {
private Boolean success;//是否成功
private Integer code;//状态码
private String message;//返回消息
private T data;//返回数据
/**
* 私有化构造方法,禁止在其它类创建对象
*/
private Result(){}
/**
* 成功执行,不返回数据
* @return
*/
public static<T> Result<T> ok(){
Result<T> result = new Result<T>();
result.setSuccess(true);
result.setCode(ResultCode.SUCCESS);
result.setMessage("执行成功");
return result;
}
/**
* 成功执行,并返回数据
* @param data
* @param <T>
* @return
*/
public static<T> Result<T> ok(T data){
Result<T> result = new Result<T>();
result.setSuccess(true);
result.setCode(ResultCode.SUCCESS);
result.setMessage("执行成功");
result.setData(data);
return result;
}
/**
* 失败
* @return
*/
public static<T> Result<T> error(){
Result<T> result = new Result<T>();
result.setSuccess(false);
result.setCode(ResultCode.ERROR);
result.setMessage("执行失败");
return result;
}
/**
* 设置是否成功
* @param success
* @return
*/
public Result<T> success(Boolean success){
this.setSuccess(success);
return this;
}
/**
* 设置状态码
* @param code
* @return
*/
public Result<T> code(Integer code){
this.setCode(code);
return this;
}
/**
* 设置返回消息
* @param message
* @return
*/
public Result<T> message(String message){
this.setMessage(message);
return this;
}
/**
* 是否存在
* @return
*/
public static<T> Result<T> exist(){
Result<T> result = new Result<T>();
result.setSuccess(true);
result.setCode(ResultCode.SUCCESS);
result.setMessage("执行成功");
return result;
}
}
package com.ftz.utils;
public class ResultCode {
/**
* 成功状态码
*/
public static final Integer SUCCESS = 200;
/**
* 失败状态码
*/
public static final Integer ERROR = 500;
/**
* 未登录状态码
*/
public static final int NO_LOGIN = 600;
/**
* 没有权限状态码
*/
public static final int NO_AUTH = 700;
}
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/justleavel/article/details/125810184
内容来源于网络,如有侵权,请联系作者删除!