1.简介
SpringSecurity是spring的一个安全管理框架,相比另一个安全框架shiro,它提供了更丰富的功能,社区资源也比shiro丰富。
web应用主要用于认证和授权
认证:验证当前访问系统的是不是本系统用户,并且要确定具体是哪个用户
授权:经过认证完成过后判断当前用户是否具有某个权限操作具体资源
springsecurity的核心就是认证和授权,并且更快的整合soring应用
2.快速入门
2.1准备工作
首先搭建一个soringBoot工程
pom依赖
org.springframework.boot spring-boot-starter-parent 2.5.0 8 8 UTF-8 org.springframework.boot spring-boot-starter-web
创建一个测试小例子
@RestControllerpublic class HelloController { @GetMapping("/hello") public String hello() { return "hello security"; }}
访问http://localhost:8080/hello, 返回字符串hello security
2.2 引入spring security pom依赖
org.springframework.boot spring-boot-starter-security
再次登录会需要输入用户名和密码,此时默认用户名为: user 密码为控制台输入的密码
Using generated security password: e493f2ae-cb1e-4802-b0a2-1e8da5d97068
3.认证
3.1 登录校验流程
3.2 原理讲解
根据入门案例,分析具体流程
3.2.1 SpringScurity完整流程
springsecurity底层实现是通过一系列的过滤器链完成登录验证和授权等功能,主要是使用到下面15个过滤器
调用流程
认证流程介绍
接口和类介绍:
UsernamePasswordAuthenticationFilter: 认证过滤器主要用于生成Authentication对象完成用户信息封装。
AuthenticationManager: 定义了认证Authentication的方法。
AbstractUserDetailsAuthenticationProvider:提供了authenticate()认证方法,并且在里面调用UserDetailsServiceloadUserByUsername查询用户信息。
UserDetailsService:获取用户信息的核心接口,里面定义了根据用户名查询用户信息,可以实现该接口覆盖默认的用户信息查询方法,完成自定义用户信息查询,返回一个UserDetails用户信息封装对象。
UserDetails: 主要封装用户相关信息,通过UserDetailsService获取用户信息返回UserDetails对象,然后将相关信息设置到Authentication对象中。
3.3 解决问题
3.3.1 思路分析
登录
自定义登录接口
调用ProviderManager的认证方法,如果认证通过生成jwt并存入到redis
自定义UserDetailsService
通过自定义的实现完成从数据库查询用户信息和权限信息并返回UserDetails对象
校验
自定义jwt认证过滤器
获取生成jwt token,解析token获取userid,通过userid从redis中获取用户信息,存入到SecurityContextHolder中,方便后续其他过滤器通过SecurityContextHolder获取用户信息
3.3.2 jwt简介
什么是 JSON Web Token?
JSON Web 令牌 (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。此信息可以验证和信任,因为它是经过数字签名的。JWT 可以使用密钥(使用 HMAC 算法)或使用 RSA 或 ECDSA 的公钥/私钥对进行签名。
尽管 JWT 可以加密以提供各方之间的保密性,但我们将专注于签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则向其他方隐藏这些声明。当使用公钥/私钥对对令牌进行签名时,签名还证明只有持有私钥的一方才是签名的一方。
跨平台 跨语言 轻量级json 对象进行数据传输, 数字签名保证安全性
应用场景:
授权:这是使用 JWT 的最常见方案。用户登录后,每个后续请求都将包含 JWT,允许用户访问使用该令牌允许的路由、服务和资源。单点登录是当今广泛使用 JWT 的一项功能,因为它的开销很小,并且能够跨不同域轻松使用。
信息交换:JSON Web 令牌是在各方之间安全传输信息的好方法。由于 JWT 可以签名(例如,使用公钥/私钥对),因此您可以确定发件人是他们所说的人。此外,由于签名是使用标头和有效负载计算的,因此您还可以验证内容是否未被篡改
JSON Web Token令牌结构
JSON Web 令牌由三部分组成,中间用 . 分割
页眉
有效荷载
签名
JWT通常如: xxxxx.yyyyy.zzzzz 组成
结构详解
页眉 headers 需要Base64进行编码
页面主要包含令牌类型(令牌类型目前只有一种 JWT)和签名算法例如HMAC SHA256 RSA
例如:
{"alg" : "SHA256","typ" : "JWT"}
有效荷载 payload 需要Base64进行编码
荷载主要包含声明,声明分为已注册声明、公共声明、专用声明
已注册声明: 由JWT提前帮我们预定义好的例如iss(发行人),exp(到期时间),sub(主题),aud(受众)等。
公共声明: 这些可以由使用 JWT 的人随意定义。但为了避免冲突,它们应该在 IANA JSON Web 令牌注册表中定义,或者定义为包含抗冲突命名空间的 URI。
专用声明: 这个由用户自定义可以是一个json字符串用户可以将实体信息转换后放入专用声明中,
注意:这些声明中不要包含任何敏感信息
例如
{ "sub": "1234567890", "name": "John Doe", "admin": true}
签名
要创建签名部分,首先需要Base64编码页眉和有效荷载json数据,然后通过页面中指定的算法以及密钥进行算法加密获取签名的结果。
密钥:是用户自定义的一系列字符串作为私钥,不能提供给其他系统
签名示例
// 头部信息{ "alg": "HS256", "typ": "JWT"}// 有效荷载{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022}// 通过指定算法签名HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), your-256-bit-secret)// 签名后的结果eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
3.3.3 java中使用jwt
public class JwtTest { // 生成jwt @Test public void contextLoads() { HashMap headers = new HashMap(); Calendar instance = Calendar.getInstance(); instance.add(Calendar.SECOND, 200); String token = JWT.create().withHeader(headers) //headers .withClaim("userid", "21") .withClaim("username", "wangm") //payload .withExpiresAt(instance.getTime()) .sign(Algorithm.HMAC256("1231312sdasddsas"));// 密钥 System.out.println(token); } // 验证jwt 可以获取页眉数据和有效荷载数据 @Test public void checkToken() { JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("1231312sdasddsas")).build(); DecodedJWT decodedJWT = jwtVerifier.verify("eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE2NzI0OTMxMTAsInVzZXJpZCI6IjIxIiwidXNlcm5hbWUiOiJ3YW5nbSJ9.DNmrAFgRnWdcAtBK6nkwNPV-GSsrKyO_TyoIeB0YqzI"); String userid = decodedJWT.getClaim("userid").asString(); System.out.println(userid); }}
jwt封装工具类
@Componentpublic class JwtUtils { private static String secretKey; @Value("${JWT.secretKey}") public void secretKey(String secretKey) { JwtUtils.secretKey = secretKey; } public static String generateToken(Map map, Integer expDay) { Calendar instance = Calendar.getInstance(); instance.add(Calendar.SECOND, 1); JWTCreator.Builder builder = JWT.create(); map.forEach((k, v) ->{ builder.withClaim(k, v); }); builder.withExpiresAt(instance.getTime()); // 生成token return builder.sign(Algorithm.HMAC256(secretKey)); } public static void verifyToken(String token) { // 验证token JWT.require(Algorithm.HMAC256(secretKey)).build().verify(token); } public static DecodedJWT getTokenInfo(String token) { DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(secretKey)).build().verify(token); return decodedJWT; }}com.jack.study.security.entity.LoginUser [Username=admin, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[admin]]
jwt整合springboot 整合拦截器统一令牌验证
// 整合拦截器实现统一token 验证public class JwtInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token = request.getHeader("token"); Map map = new HashMap<>(); if (StrUtil.isEmpty(token)) { map.put("status", "514"); map.put("message", "token为空"); String s = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.setCharacterEncoding("UTF-8"); response.getWriter().write(s); return false; } // jwt异常处理 try{ JwtUtils.verifyToken(token); return true; }catch (SignatureVerificationException e1) { map.put("status", "510"); map.put("message", "签名异常"); }catch (TokenExpiredException e2) { map.put("status", "511"); map.put("message", "token已过期"); }catch (AlgorithmMismatchException e3) { map.put("status", "512"); map.put("message", "加密算法异常"); }catch (JWTDecodeException e4) { map.put("status", "513"); map.put("message", "token解密异常"); }catch (Exception e) { map.put("status", "500"); map.put("message", "系统异常"); } String s = new ObjectMapper().writeValueAsString(map); response.setContentType("application/json;charset=UTF-8"); response.setCharacterEncoding("UTF-8"); response.getWriter().write(s); return false; }}
3.3.4 准备工作
创建一个maven 工程 添加相关依赖
pom.xml
4.0.0 com.jack.study spring-security-token 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-parent 2.5.0 8 8 UTF-8 org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-test org.springframework.boot spring-boot-starter-security org.springframework.boot spring-boot-starter-data-redis io.jsonwebtoken jjwt 0.9.0 com.alibaba fastjson 1.2.83 com.github.xiaoymin swagger-bootstrap-ui 1.9.6 io.springfox springfox-swagger2 2.9.2 io.swagger swagger-models io.swagger swagger-models 1.5.21 cn.hutool hutool-all 5.6.2 org.projectlombok lombok true mysql mysql-connector-java com.baomidou mybatis-plus-boot-starter 3.4.3
创建启动类 并配置相关扫描
@SpringBootApplication(scanBasePackages = {"com.jack.study"}) // 组件扫描路径@MapperScan(basePackages = {"com.jack.study.*.mapper"}) // mapper文件扫描public class SecurityTokenApplication { public static void main(String[] args) { SpringApplication.run(SecurityTokenApplication.class, args); }}
定义yml配置
server: port: 8080spring:#数据库连接配置 datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/security-jwt?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=UTC username: root password: 123456 #redis 连接配置 redis: host: localhost port: 6379 password: 123456mybatis-plus:#mapper xml扫描配置 mapper-locations: classpath:/mapper //请求成功 SUCCESS(200,"成功"), PARAM_IS_INVALID(1001,"参数为空"), OK(200, "操作成功"), FAIL(500, "操作失败"), NO_AUTH(403, "没有权限"), NO_PAGE(404, "未找到页面"), TOKEN_OVERDUE(400, "Token超期"); //参数校检 private Integer code; private String message; ResultCode(Integer code,String message){ this.code = code; this.message = message; } public Integer code(){ return this.code; } public String message(){ return this.message; }}// 响应结果实体@Datapublic class Result implements Serializable { private Integer code; private String message; private Object data; public Result(){ } //自定义错误代码和错误信息 public Result(Integer code, String message, Object data) { this.code = code; this.message = message; this.data = data; } //根据枚举固定错误代码获取 public Result(ResultCode resultCode, Object data){ this.code = resultCode.code(); this.message = resultCode.message(); this.data = data; } public static Result success(){ Result result = new Result(); result.setCode(ResultCode.SUCCESS.code()); result.setMessage(ResultCode.SUCCESS.message()); return result; } public static Result success(Object data){ Result result = new Result(); result.setCode(ResultCode.SUCCESS.code()); result.setMessage(ResultCode.SUCCESS.message()); result.setData(data); return result; } public static Result failure(ResultCode resultCode){ Result result = new Result(); result.setCode(resultCode.code()); result.setMessage(resultCode.message()); return result; } public static Result failure(Integer code, String message){ Result result = new Result(); result.setCode(code); result.setMessage(message); return result; } public static Result failure(ResultCode resultCode,Object data){ Result result = new Result(); result.setCode(resultCode.code()); result.setMessage(resultCode.message()); result.setData(data); return result; } public static Result custom(Integer code, String message, Object data){ Result result = new Result(); result.setCode(code); result.setMessage(message); result.setData(data); return result; }}
异常封装统一处理
认证异常工具类
public class AuthExceptionUtil { public static Result getErrMsgByExceptionType(AuthenticationException e) { if (e instanceof LockedException) { return Result.failure(1100, "账户被锁定,请联系管理员!"); } else if (e instanceof CredentialsExpiredException) { return Result.failure(1105,"用户名或者密码输入错误!"); }else if (e instanceof InsufficientAuthenticationException) { return Result.failure(403,"权限不足请登录!"); } else if (e instanceof AccountExpiredException) { return Result.failure(1101, "账户过期,请联系管理员!"); } else if (e instanceof DisabledException) { return Result.failure(1102, ("账户被禁用,请联系管理员!")); } else if (e instanceof BadCredentialsException) { return Result.failure(1105, "用户名或者密码输入错误!"); }else if (e instanceof AuthenticationServiceException) { return Result.failure(1106, "认证失败,请重试!"); } return Result.failure(1200, e.getMessage()); } public static Result getErrMsgByExceptionType(AccessDeniedException e) { if (e instanceof CsrfException) { return Result.failure(-1001, "非法访问跨域请求异常!"); } else if (e instanceof org.springframework.security.web.csrf.CsrfException) { return Result.failure(-1002,"非法访问跨域请求异常!"); } else if (e instanceof AuthorizationServiceException) { return Result.failure(1101, "认证服务异常请重试!"); }else if (e instanceof AccessDeniedException) { return Result.failure(4003, "权限不足不允许访问!"); } return Result.failure(1200, e.getMessage()); }}
认证业务异常封装
public class BusinessException extends AuthenticationException { public BusinessException(String msg, Throwable cause) { super(msg, cause); } public BusinessException(String msg) { super(msg); }}
统一异常处理,根据自定义异常分别处理错误信息
@RestControllerAdvicepublic class GlobalExceptionHandler {// 根据实际自定义的异常统一处理 @ExceptionHandler(value = BusinessException.class) public Result operationError(BusinessException e) { return Result.failure(ResultCode.FAIL, e.getMessage()); }}
redisConfig 配置类
@Configurationpublic class RedisConfig { public RedisTemplate
fastjoson 序列化
public class FastJsonRedisSerializer implements RedisSerializer { public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); private Class clazz; static { ParserConfig.getGlobalInstance().setAutoTypeSupport(true); } public FastJsonRedisSerializer(Class clazz) { super(); this.clazz = clazz; } public byte[] serialize(T t) throws SerializationException { if (t == null) { return new byte[0]; } return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET); } public T deserialize(byte[] bytes) throws SerializationException { if (bytes == null || bytes.length <= 0) { return null; } String str = new String(bytes, DEFAULT_CHARSET); return JSON.parseObject(str, clazz); } protected JavaType getJavaType(Class> clazz) { return TypeFactory.defaultInstance().constructType(clazz); }}
redis 工具类封装
@Component@Order(-1)public final class RedisUtil { @Autowired private RedisTemplate redisTemplate; public boolean expire(String key, long time) { try { if (time > 0) { redisTemplate.expire(key, time, TimeUnit.SECONDS); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } public long getExpire(String key) { return redisTemplate.getExpire(key, TimeUnit.SECONDS); } public boolean hasKey(String key) { try { return redisTemplate.hasKey(key); } catch (Exception e) { e.printStackTrace(); return false; } } @SuppressWarnings("unchecked") public void del(String... key) { if (key != null && key.length > 0) { if (key.length == 1) { redisTemplate.delete(key[0]); } else { redisTemplate.delete(CollectionUtils.arrayToList(key)); } } } // ============================String============================= public Object get(String key) { return key == null ? null : redisTemplate.opsForValue().get(key); } public boolean set(String key, Object value) { try { redisTemplate.opsForValue().set(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } public boolean set(String key, Object value, long time) { try { if (time > 0) { redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS); } else { set(key, value); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } public long incr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递增因子必须大于0"); } return redisTemplate.opsForValue().increment(key, delta); } public long decr(String key, long delta) { if (delta < 0) { throw new RuntimeException("递减因子必须大于0"); } return redisTemplate.opsForValue().increment(key, -delta); } // ================================Map================================= public Object hget(String key, String item) { return redisTemplate.opsForHash().get(key, item); } public Map hmget(String key) { return redisTemplate.opsForHash().entries(key); } public boolean hmset(String key, Map map) { try { redisTemplate.opsForHash().putAll(key, map); return true; } catch (Exception e) { e.printStackTrace(); return false; } } public boolean hmset(String key, Map map, long time) { try { redisTemplate.opsForHash().putAll(key, map); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } public boolean hset(String key, String item, Object value) { try { redisTemplate.opsForHash().put(key, item, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } public boolean hset(String key, String item, Object value, long time) { try { redisTemplate.opsForHash().put(key, item, value); if (time > 0) { expire(key, time); } return true; } catch (Exception e) { e.printStackTrace(); return false; } } public void hdel(String key, Object... item) { redisTemplate.opsForHash().delete(key, item); } public boolean hHasKey(String key, String item) { return redisTemplate.opsForHash().hasKey(key, item); } public double hincr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, by); } public double hdecr(String key, String item, double by) { return redisTemplate.opsForHash().increment(key, item, -by); } // ============================set============================= public Set sGet(String key) { try { return redisTemplate.opsForSet().members(key); } catch (Exception e) { e.printStackTrace(); return null; } } public boolean sHasKey(String key, Object value) { try { return redisTemplate.opsForSet().isMember(key, value); } catch (Exception e) { e.printStackTrace(); return false; } } public long sSet(String key, Object... values) { try { return redisTemplate.opsForSet().add(key, values); } catch (Exception e) { e.printStackTrace(); return 0; } } public long sSetAndTime(String key, long time, Object... values) { try { Long count = redisTemplate.opsForSet().add(key, values); if (time > 0) expire(key, time); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } public long sGetSetSize(String key) { try { return redisTemplate.opsForSet().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } public long setRemove(String key, Object... values) { try { Long count = redisTemplate.opsForSet().remove(key, values); return count; } catch (Exception e) { e.printStackTrace(); return 0; } } // ===============================list================================= public List lGet(String key, long start, long end) { try { return redisTemplate.opsForList().range(key, start, end); } catch (Exception e) { e.printStackTrace(); return null; } } public long lGetListSize(String key) { try { return redisTemplate.opsForList().size(key); } catch (Exception e) { e.printStackTrace(); return 0; } } public Object lGetIndex(String key, long index) { try { return redisTemplate.opsForList().index(key, index); } catch (Exception e) { e.printStackTrace(); return null; } } public boolean lSet(String key, Object value) { try { redisTemplate.opsForList().rightPush(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } public boolean lSet(String key, Object value, long time) { try { redisTemplate.opsForList().rightPush(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } } public boolean lSet(String key, List value) { try { redisTemplate.opsForList().rightPushAll(key, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } public boolean lSet(String key, List value, long time) { try { redisTemplate.opsForList().rightPushAll(key, value); if (time > 0) expire(key, time); return true; } catch (Exception e) { e.printStackTrace(); return false; } } public boolean lUpdateIndex(String key, long index, Object value) { try { redisTemplate.opsForList().set(key, index, value); return true; } catch (Exception e) { e.printStackTrace(); return false; } } public long lRemove(String key, long count, Object value) { try { Long remove = redisTemplate.opsForList().remove(key, count, value); return remove; } catch (Exception e) { e.printStackTrace(); return 0; } }}
webUtil response 响应封装
public class WebUtils { public static String rednerString(HttpServletResponse response, String content) { try{ response.setStatus(200); response.setContentType("application/json;charset=utf-8"); response.setCharacterEncoding("UTF-8"); response.getWriter().print(content); }catch (Exception e){ e.printStackTrace(); } return null; }}
3.3.5 实现
3.3.5.1 准备数据库用户
从前面的分析可以知道我们自定义个UserDetailsService 实现类即可完成从数据库通过用户名和密码完成认证
创建用户表
CREATE TABLE `user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户名', `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '密码', `nickname` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '昵称', `status` varchar(50) NOT NULL DEFAULT '0' COMMENT '账号状态(0 正常 1 停用)', `email` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '邮箱', `phonenumber` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '联系方式', `sex` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户性别(0 男 1 女 2未知)', `avatar` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '头像', `user_type` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '用户类型(0 管理员 1 普通用户)', `create_by` bigint DEFAULT NULL COMMENT '创建人id', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_by` bigint DEFAULT NULL COMMENT '更新人id', `update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间', `del_flag` int NOT NULL DEFAULT '0' COMMENT '删除标志(0 未删除 1 已删除)', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表';
3.3.5.2 创建操作用户相关接口和类
用户实体类
@Data@NoArgsConstructor@AllArgsConstructor@TableName("user")public class UserDO implements Serializable { private static final long serialVersionUID = -7637085672143873931L; private Long id; private String username; private String password; private String nickname; private String status; private String email; private String phonenumber; private String sex; private String avatar; private String userType; private Long createBy; private LocalDateTime createTime; private Long updateBy; private LocalDateTime updateTime; private Integer delFlag;}
定义UserMapper和相关方法
public interface UserMapper extends BaseMapper {}
定义UserService和相关方法
public interface UserService extends IService { UserDO getUserByUsername(String username);}
定义UserServiceImpl和相关方法
@Servicepublic class UserServiceImpl extends ServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public UserDO getUserByUsername(String username) { UserDO userDO = userMapper.selectOne(new QueryWrapper().lambda().eq(UserDO::getUsername, username)); if (Objects.isNull(userDO)) { throw new BusinessException("用户信息不存在!"); } return userDO; }}
3.3.5.3 创建操作角色接口和相关类
创建角色表
CREATE TABLE `sys_role` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', `name` varchar(128) NOT NULL COMMENT '角色名称', `role_key` varchar(100) NOT NULL COMMENT '角色代码', `status` char(1) NOT NULL DEFAULT '0' COMMENT '角色状态(0 正常 1 停用)', `del_flag` int NOT NULL DEFAULT '0' COMMENT '删除标志(0 正常 -1 已删除)', `create_by` bigint DEFAULT NULL COMMENT '创建人', `update_by` bigint DEFAULT NULL COMMENT '更新人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` timestamp NULL DEFAULT NULL COMMENT '更新时间', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='角色表';
创建SysRole实体类
@Data@NoArgsConstructor@AllArgsConstructorpublic class SysRole { private Long id; private String name; private String roleKey; private String status; private Integer delFlag; private Long createBy; private Long updateBy; private LocalDateTime createTime; private LocalDateTime updateTime; private String remark;}
创建SysRoleMapper接口
public interface SysRoleMapper extends BaseMapper { List selectRolesByUserId(@Param("userId") Long userId);}
创建SysRoleService 接口
public interface SysRoleService extends IService { List selectRolesByUserId(@Param("userId") Long userId);}
创建SysRoleService 接口实现类SysRoleServiceImpl
@Servicepublic class SysRoleServiceImpl extends ServiceImpl implements SysRoleService { @Autowired private SysRoleMapper sysRoleMapper; @Override public List selectRolesByUserId(Long userId) { return sysRoleMapper.selectRolesByUserId(userId); }}
创建SysRoleMapper.xml
3.3.5.4 创建操作菜单接口和相关类
创建角色表
CREATE TABLE `sys_menu` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键id', `menu_name` varchar(64) NOT NULL COMMENT '菜单名称', `path` varchar(200) NOT NULL COMMENT '菜单路径', `component` varchar(255) NOT NULL COMMENT '组件', `visible` char(1) NOT NULL DEFAULT '0' COMMENT '菜单显示(0 显示 1 隐藏)', `status` char(1) NOT NULL DEFAULT '0' COMMENT '菜单状态(0 启用 1 停用)', `perms` varchar(100) NOT NULL COMMENT '权限集合标识', `icon` varchar(100) DEFAULT NULL COMMENT '图标', `create_by` bigint DEFAULT NULL COMMENT '创建人', `update_by` bigint DEFAULT NULL COMMENT '更新人', `create_time` datetime DEFAULT NULL COMMENT '创建时间', `update_time` datetime DEFAULT NULL COMMENT '更新时间', `del_flag` int NOT NULL DEFAULT '0' COMMENT '删除标志(0 正常 -1 已删除)', `remark` varchar(500) DEFAULT NULL COMMENT '备注', PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='菜单表';
创建SysMenu实体类
@Data@NoArgsConstructor@AllArgsConstructorpublic class SysMenu { private Long id; private String menuName; private String path; private String component; private String visible; private String status; private String perms; private String icon; private Long createBy; private Long updateBy; private LocalDateTime createTime; private LocalDateTime updateTime; private Integer delFlag; private String remark;}
创建SysMenuMapper接口
public interface SysMenuMapper extends BaseMapper { List selectPermsByUserId(@Param("userId") Long userId);}
创建SysMenuService接口
public interface SysMenuService extends IService { List selectPermsByUserId(Long userId);}
创建SysMenuService接口实现类SysMenuServiceImpl
@Servicepublic class SysMenuServiceImpl extends ServiceImpl implements SysMenuService { @Autowired private SysMenuMapper sysMenuMapper; @Override public List selectPermsByUserId(Long userId) { return sysMenuMapper.selectPermsByUserId(userId); }}
创建SysMenuMapper.xml
3.3.5.5 创建用户角色关联
用户角色sql
CREATE TABLE `sys_user_role` ( `user_id` bigint NOT NULL AUTO_INCREMENT COMMENT '用户id', `role_id` bigint NOT NULL COMMENT '角色id', PRIMARY KEY (`user_id`,`role_id`)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户角色关联表';
用户角色实体
@Datapublic class SysUserRole { private Long userId; private Long roleId;}
3.3.5.6创建 角色菜单关联
角色菜单sql
CREATE TABLE `sys_role_menu` ( `role_id` bigint NOT NULL AUTO_INCREMENT COMMENT 'roleId', `menu_id` bigint NOT NULL COMMENT 'menuId', PRIMARY KEY (`role_id`,`menu_id`)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='sys_role_menu';
3.3.5.67登录登出接口
登录登出LoginService
public interface LoginService { Result login(UserDto userDto); Result logout();}
@Servicepublic class LoginServiceImpl implements LoginService { @Autowired private RedisUtil redisUtil; @Autowired private AuthenticationManager authenticationManager; @Override public Result login(UserDto userDto) { // 1 获取AuthenticationManager 对象 然后调用 authenticate() 方法 // UsernamePasswordAuthenticationToken 实现了Authentication 接口 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDto.getUsername(), userDto.getPassword()); Authentication authenticate = authenticationManager.authenticate(authenticationToken); //2 认证没通过 提示认证失败 if (Objects.isNull(authenticate)) { throw new BusinessException("认证失败用户信息不存在"); } //认证通过 使用userid 生成jwt token令牌 LoginUser loginUser = (LoginUser) authenticate.getPrincipal(); String s = loginUser.getUserDO().getId().toString(); String token = JwtUtils.createJwt(s); // 把用户信息存入到redis中 Map map = new HashMap<>(); map.put("token", token); redisUtil.set("login:"+s, loginUser); return Result.success(map); } @Override public Result logout() { // 1 获取 SecurityContextHolder 中的用户id UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken)SecurityContextHolder.getContext().getAuthentication(); LoginUser loginUser = (LoginUser)authentication.getPrincipal(); //2 删除redis 中的缓存信 String key = "login:"+loginUser.getUserDO().getId().toString(); redisUtil.del(key); return Result.success("退出成功!"); }}
3.3.6 SpringSecurity 配置重写
3.3.6.1 自定义UserDetailsService实现MyUserDetailsService
具体配置需要将其他组件写好再配置
@Servicepublic class MyUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return null; }}
3.3.6.2 自定义WebSecurityConfigurerAdapter 实现类SecurityConfig
具体配置需要将其他组件写好再配置
@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class SecurityConfig extends WebSecurityConfigurerAdapter {}
3.3.6.3 定义认证和授权相关组件
再认证过程中通过自定义的UserDetailsService,重写了loadUserByUsername 方法返回UserDetails对象,我们可以通过实现UserDetails 完成自定义用户信息返回
自定义LoginUser对象
@Data@NoArgsConstructorpublic class LoginUser implements UserDetails{// 用户信息 private UserDO userDO;// 权限信息 private List permissions; @JSONField(serialize = false) private List authorities; public LoginUser(UserDO userDO, List permissions){ this.userDO = userDO; this.permissions = permissions; } @Override public Collection extends GrantedAuthority> getAuthorities() { // 将权限信息封装成 SimpleGrantedAuthority if (authorities != null) { return authorities; } authorities = this.permissions.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()); return authorities; } @Override public String getPassword() { return this.userDO.getPassword(); } @Override public String getUsername() { return this.userDO.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; }}
自定义MyUserDetailsService,实现自定义的用户名密码校验
@Servicepublic class MyUserDetailsService implements UserDetailsService {// 用户信息 @Autowired private UserService userService; //权限菜单信息 @Autowired private SysMenuService sysMenuService; //角色信息 @Autowired private SysRoleService sysRoleService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { UserDO userDO = userService.getUserByUsername(username); // 获取用户所有的角色 List roles = sysRoleService.selectRolesByUserId(userDO.getId()); Set set = roles.stream().map(s -> "ROLE_" + s).collect(Collectors.toSet()); // 获取用户所有的权限 List permissions = sysMenuService.selectPermsByUserId(userDO.getId()); permissions.addAll(set); return new LoginUser(userDO, permissions); }}
3.3.6.4 自定义认证异常和授权异常处理
自定义认证失败处理AuthenticationEntryPointImpl
@Componentpublic class AuthenticationEntryPointImpl implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { WebUtils.rednerString(httpServletResponse, JSONUtil.toJsonStr(AuthExceptionUtil.getErrMsgByExceptionType(e))); }}
自定义授权失败处理AccessDeniedHandlerImpl
@Componentpublic class AccessDeniedHandlerImpl implements AccessDeniedHandler { @Override public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException { WebUtils.rednerString(httpServletResponse, JSONUtil.toJsonStr(AuthExceptionUtil.getErrMsgByExceptionType(e))); }}
3.3.6.5 整合jwt,自定义JWT认证过滤器
自定义jwt 认证token过滤器JwtAuthenticationFilter
@Componentpublic class JwtAuthenticationFilter extends OncePerRequestFilter { @Autowired private RedisUtil redisUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws BusinessException, ServletException, IOException { // 获取token String token = request.getHeader("token"); if (StrUtil.isEmpty(token)) { // token不存在 放行 并且直接return 返回 filterChain.doFilter(request, response); return; } // 解析token String userId = null; try { Claims claims = JwtUtils.parseJwt(token); userId = claims.getSubject(); } catch (Exception e) { throw new BusinessException("token非法"); } // 获取userid 从redis中获取用户信息 String redisKey = "login:" + userId; LoginUser loginUser = (LoginUser)redisUtil.get(redisKey); if (Objects.isNull(loginUser)) { throw new BusinessException("用户未登录"); } //将用户信息存入到SecurityContextHolder UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authenticationToken); // 放行 filterChain.doFilter(request, response); }}
3.3.6.6 跨域请求配置
自定义CorsConfig配置
@Configurationpublic class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowCredentials(true) .allowedMethods("GET", "POST", "PUT", "DELETE") .maxAge(3600); } @Bean public CorsConfiguration corsConfiguration() { CorsConfiguration corsConfiguration = new CorsConfiguration(); corsConfiguration.addAllowedOrigin("*"); corsConfiguration.addAllowedHeader("*"); corsConfiguration.addAllowedMethod("*"); corsConfiguration.addExposedHeader("token"); return corsConfiguration; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", corsConfiguration()); return new CorsFilter(source); }}
3.3.6.7 整合到SpringSecurity
SpringSecurity核心配置
@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled = true)public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private JwtAuthenticationFilter jwtAuthenticationFilter; @Autowired private AccessDeniedHandlerImpl accessDeniedHandler; @Autowired private AuthenticationEntryPointImpl authenticationEntryPoint; // 自定义密码加密配置常用的BCryptPasswordEncoder,也可以用其他的 @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override protected void configure(HttpSecurity http) throws Exception { http // 关闭csrf .csrf().disable() // 不通过session 获取SecurityContext .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() // 允许登录接口匿名访问 .antMatchers("/user/login", "/user/test").anonymous() // 其他请求都需要认证 .anyRequest().authenticated(); // 在UsernamePasswordAuthenticationFilter 过滤器之前执行jwtAuthenticationFilter http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class); // 认证授权异常自定义处理 http.exceptionHandling() .authenticationEntryPoint(authenticationEntryPoint) .accessDeniedHandler(accessDeniedHandler); // 跨域请求配置 http.cors(); }}
3.3.6.8 测试
编写LoginController
@RestControllerpublic class LoginController { @Autowired private LoginService loginService; // 登录接口 需要传入用户名和密码 @PostMapping("/user/login") public Result login(@RequestBody UserDto userDto) { Result result = loginService.login(userDto); return result; } @PostMapping("/user/hello") public Result login() { return Result.success(); } //等处配置 @GetMapping("/user/logout") public Result logout() { return loginService.logout(); }}
使用postman工具进行测试
登录接口测试
退出接口测试,需要在请求头中携带token
鉴权测试
当前用户权限为编码
select sm.perms from sys_user_role sur left join sys_role sr on sr.id = sur.role_id left join sys_role_menu srm on srm.role_id = sr.id left join sys_menu sm on sm.id = srm.menu_id where sr.`status`='0' and sm.status = 0 and sur.user_id=1 group by sm.perms
@RestControllerpublic class HelloController { @GetMapping("/hello/test") // 只有system:dept:list:test 权限才能访问 @PreAuthorize("hasAuthority('system:dept:list:test')") public String hello() { return "hello"; }}
请求头中携带登录token,测试结果
正常访问
@RestControllerpublic class HelloController { @GetMapping("/hello/test") // 只有system:dept:list:test 权限才能访问 @PreAuthorize("hasAuthority('system:dept:list')") public String hello() { return "hello"; }}
返回 字符串 "hello"
其他鉴权测试同上,下面讲解其他鉴权的含义
@RestControllerpublic class HelloController { @GetMapping("/hello/test") //必须包含system:dept:list 权限才能访问否则提示没有权限 //@PreAuthorize("hasAuthority('system:dept:list')") // 只需要有其中一个权限就能访问 //@PreAuthorize("hasAnyAuthority('system:dept:list555','system:dept:list')") //只需要包含其中一个角色就能访问//@PreAuthorize("hasAnyRole('ceo', 'admin')") //必须包含指定角色才能访问 //@PreAuthorize("hasRole('ceo')") // 自定义权限判断 @PreAuthorize("@ex.hasAuthority('ROLE_ceo')") public String hello() { return "hello"; }}
3.3.6.9 自定义权限判断
源码探究
不管是hasAuthority 、hasAnyAuthority、 hasAnyRole、 hasRole都是在SecurityExpressionRoot类中实现
SecurityExpressionRoot
public final boolean hasAuthority(String authority) { return this.hasAnyAuthority(authority); } public final boolean hasAnyAuthority(String... authorities) { return this.hasAnyAuthorityName((String)null, authorities); } public final boolean hasRole(String role) { return this.hasAnyRole(role); } public final boolean hasAnyRole(String... roles) { return this.hasAnyAuthorityName(this.defaultRolePrefix, roles); } private boolean hasAnyAuthorityName(String prefix, String... roles) { //获取当前用户的所有权限和角色 Set roleSet = this.getAuthoritySet(); String[] var4 = roles; int var5 = roles.length; for(int var6 = 0; var6 < var5; ++var6) { String role = var4[var6]; String defaultedRole = getRoleWithDefaultPrefix(prefix, role); // 如果当前用户里面包含注解里面的角色或者权限则返回true 允许访问 if (roleSet.contains(defaultedRole)) { return true; } }//返回false 不允许访问 return false; }
this.getAuthoritySet() 源码探究
private Set getAuthoritySet() { if (this.roles == null) { // 这里是从authentication 中获权限,在前面认证授权过程中我们将从数据查询道德角色权限信息通过自定义的LoginUser 设置到authentication 中,此时用户就能拿到对应的角色权限信息 //JwtAuthenticationFilter 过滤器中将用户信息存入到SecurityContextHolder // UsernamePasswordAuthenticationToken authenticationToken = new //UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities()); // SecurityContextHolder.getContext().setAuthentication(authenticationToken); Collection extends GrantedAuthority> userAuthorities = this.authentication.getAuthorities(); if (this.roleHierarchy != null) { userAuthorities = this.roleHierarchy.getReachableGrantedAuthorities(userAuthorities); }//转换为set集合 this.roles = AuthorityUtils.authorityListToSet(userAuthorities); } return this.roles; }
通过上面的源码我们知道 只需要在鉴权的时候返回true或false 就能实现鉴权访问
自定义JackExpressionRoot
@Component("ex")public class JackExpressionRoot { public final boolean hasAuthority(String authority) { LoginUser loginUser = (LoginUser)SecurityContextHolder.getContext().getAuthentication().getPrincipal(); List permissions = loginUser.getPermissions(); return permissions.contains(authority); }}
我们只需要按照security 鉴权判断方式编写即可实现自定义鉴权
//实现@加组件名称.方法名('权限编码或ROLE_角色编码')@PreAuthorize("@ex.hasAuthority('ROLE_ceo')") public String hello() { return "hello"; }
3.3.7 扩展说明
3.3.7.1 自定义认证 授权 登出
在SecurityConfig 中我们也可以去自定义认证成功或失败,以及登出成功实现自定义处理
自定义认证成功处理MySuccessHandler
@Componentpublic class MySuccessHandler implements AuthenticationSuccessHandler { @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException { AuthenticationSuccessHandler.super.onAuthenticationSuccess(request, response, chain, authentication); } @Override public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { System.out.println("认证成功调用"); }}
自定义认证失败处理
@Componentpublic class MyFailureHandler implements AuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException { System.out.println("认证失败的时候调用"); }}
自定义登出成功处理MyLogoutSuccessHandler
@Componentpublic class MyLogoutSuccessHandler implements LogoutSuccessHandler { @Override public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException { System.out.println("注销成功时调用"); }}
将自定义的组件配置到SecurityConfig中
@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MySuccessHandler successHandler; @Autowired private MyFailureHandler failureHandler; @Autowired private MyLogoutSuccessHandler logoutSuccessHandler; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 认证成功 调用 .successHandler(successHandler) // 认证失败调用 .failureHandler(failureHandler) .and() // 登出成功调用 .logout().logoutSuccessHandler(logoutSuccessHandler) .and().authorizeRequests().anyRequest().authenticated(); }}
3.3.7.2 增加验证码
验证码应该时在登录之前进行验证,因此可以通过过滤器实现
@Componentpublic class CaptchaFilter extends OncePerRequestFilter { //保存验证码的方式 redis session @Value("${application.captcha.type}") private String saveType; @Autowired private RedisUtil redisUtil; @Autowired private LoginFailureHandler failureHandler; @Autowired private LoginSuccessHandler successHandler; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String url = request.getRequestURI(); if ("/login".equals(url) && "POST".equalsIgnoreCase(request.getMethod())) { //校验验证码 try{ validate(request); }catch (CaptchaException e) { //失败单独处理交给认证失败处理器 failureHandler.onAuthenticationFailure(request, response, e); } } // 验证通过放行 filterChain.doFilter(request, response); } public void validate (HttpServletRequest request) { //form提交的code String uuid = request.getParameter("uuid"); String code = request.getParameter("code"); String key = ""; if (StrUtil.isEmpty(code) || StrUtil.isEmpty(uuid)) { throw new CaptchaException("验证码错误"); } if("SESSION".equals(saveType)){ key = CommonConstant.API_USER_IMAGE_CODE_KEY+saveType+uuid; Object redisCode = request.getSession().getAttribute(key); if (StrUtil.isBlankIfStr(redisCode) || !(redisCode).equals(code)) { throw new CaptchaException("验证码错误"); } // 一次性使用验证完成删除 request.getSession().removeAttribute(key); }else{ key = CommonConstant.API_USER_IMAGE_CODE_KEY+saveType+uuid; Object redisCode = redisUtil.get(key); if (StrUtil.isBlankIfStr(redisCode) || !(redisCode).equals(code)) { throw new CaptchaException("验证码错误"); } // 一次性使用验证完成删除 redisUtil.del(key); } }}
也可以通过继承BasicAuthenticationFilter 实现jwt 认证过滤器
public class JwtAuthenticationFilter extends BasicAuthenticationFilter { @Autowired private JwtUtil jwtUtil; @Autowired private UserDetailServiceImp userDetailsService; @Autowired private ISysUserService sysUserService; public JwtAuthenticationFilter(AuthenticationManager authenticationManager) { super(authenticationManager); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { String header = request.getHeader(jwtUtil.getHeader()); if (StrUtil.isBlankOrUndefined(header)) { chain.doFilter(request, response); return; } Claims claims = jwtUtil.getClaimsByToken(header); if (claims == null) { throw new JwtException("token异常"); } if (jwtUtil.isTokenExpired(claims)) { throw new JwtException("token已过期"); } // 获取用户名 String username = claims.getSubject(); //获取用户权限信息 SysUser sysUser = sysUserService.getByUsername(username); if (sysUser != null) { UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, userDetailsService.getUserGrantedAuthority(sysUser.getId(), username)); SecurityContextHolder.getContext().setAuthentication(token); } else { // 将token存入Authentication中 UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, null, null); SecurityContextHolder.getContext().setAuthentication中(token); } chain.doFilter(request, response); }}
@Configuration@EnableWebSecuritypublic class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private MySuccessHandler successHandler; @Autowired private MyFailureHandler failureHandler; @Autowired private MyLogoutSuccessHandler logoutSuccessHandler; @Override protected void configure(HttpSecurity http) throws Exception { http.formLogin() // 认证成功 调用 .successHandler(successHandler) // 认证失败调用 .failureHandler(failureHandler) .and() // 登出成功调用 .logout().logoutSuccessHandler(logoutSuccessHandler) .and().authorizeRequests().anyRequest().authenticated() .addFilter(jwtAuthenticationFilter()) // 在UsernamePasswordAuthenticationFilter 先执行验证码过滤器 .addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class); }}
来源地址:https://blog.csdn.net/seeyouagain_/article/details/128549455