文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

SpringBoot+SpringSecurity+JWT整合

2023-09-06 20:18

关注

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 思路分析

登录

  1. 自定义登录接口

调用ProviderManager的认证方法,如果认证通过生成jwt并存入到redis

  1. 自定义UserDetailsService

通过自定义的实现完成从数据库查询用户信息和权限信息并返回UserDetails对象

校验

  1. 自定义jwt认证过滤器

获取生成jwt token,解析token获取userid,通过userid从redis中获取用户信息,存入到SecurityContextHolder中,方便后续其他过滤器通过SecurityContextHolder获取用户信息

3.3.2 jwt简介

  1. 什么是 JSON Web Token?

JSON Web 令牌 (JWT) 是一种开放标准 (RFC 7519),它定义了一种紧凑且独立的方式,用于在各方之间以 JSON 对象的形式安全地传输信息。此信息可以验证和信任,因为它是经过数字签名的。JWT 可以使用密钥(使用 HMAC 算法)或使用 RSAECDSA 的公钥/私钥对进行签名。

尽管 JWT 可以加密以提供各方之间的保密性,但我们将专注于签名令牌。签名令牌可以验证其中包含的声明的完整性,而加密令牌则向其他方隐藏这些声明。当使用公钥/私钥对对令牌进行签名时,签名还证明只有持有私钥的一方才是签名的一方。

跨平台 跨语言 轻量级json 对象进行数据传输, 数字签名保证安全性

应用场景:

  1. JSON Web Token令牌结构

JSON Web 令牌由三部分组成,中间用 . 分割

页眉

有效荷载

签名

JWT通常如: xxxxx.yyyyy.zzzzz 组成

  1. 结构详解

页眉 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数据,然后通过页面中指定的算法以及密钥进行算法加密获取签名的结果。

密钥:是用户自定义的一系列字符串作为私钥,不能提供给其他系统

  1. 签名示例

// 头部信息{  "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 准备工作

  1. 创建一个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            
  1. 创建启动类 并配置相关扫描

@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);    }}
  1. 定义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 redisTemplate(RedisConnectionFactory connectionFactory) {        RedisTemplate template = new RedisTemplate<>();        template.setConnectionFactory(connectionFactory);        FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);        // 使用StringRedisSerializer来序列化和反序列化redis的key值        template.setKeySerializer(new StringRedisSerializer());        template.setValueSerializer(serializer);        // Hash的key也采用StringRedisSerializer的序列化方式        template.setHashKeySerializer(new StringRedisSerializer());        template.setHashValueSerializer(serializer);        template.afterPropertiesSet();        return template;    }}

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

        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            sr.`status`='0'        and sm.status = 0                    and sur.user_id=#{userId}                group by sm.perms
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 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 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

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 资料下载
  • 历年真题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容
咦!没有更多了?去看看其它编程学习网 内容吧