栏目分类:
子分类:
返回
终身学习网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
终身学习网 > IT > 软件开发 > 后端开发 > Java

【Spring Security 实战 】Spring Security 整合 jwt 附源码

Java 更新时间:发布时间: 百科书网 趣学号

【Spring Security 实战 】Spring Security 整合 jwt 附源码
  • Spring Security 快速入门
    • 流程分析
    • 原理分析
  • Spring Security 整合 JWT
    • JWT 简介
    • Spring Security 整合 JWT 原理
    • 代码实现
      • 配置类编写
      • 增加 jwtAuthenticationTokenFilter
      • 登录流程实现
      • 登录接口实现
      • 授权流程
      • 代码实现

Spring Security 整合 jwt 源码地址

Spring Security 快速入门
  1. 新建springBoot工程导入如下依赖
    包括了后续需要用到的数据库操作相关的组件

        
        
            org.springframework.boot
            spring-boot-starter-security
        
    	
        
            com.alibaba
            druid-spring-boot-starter
            1.1.10
        
    
        
        
            mysql
            mysql-connector-java
        
    
        
        
            com.alibaba
            druid-spring-boot-starter
            ${druid.version}
        
    
        
        
            com.baomidou
            mybatis-plus-boot-starter
        
    
        
        
            org.springframework.boot
            spring-boot-starter-web
        
    
  2. 增加一个接口

    @RestController
    @RequestMapping("/user")
    @RequiredArgsConstructor
    public class UserController {
    
        private final UserService userService;
    
        @GetMapping("/{id}")
        public R queryById(@PathVariable Long id){
    
            new FilterChainProxy();
            return R.ok( userService.queryById(id));
        }
    
    }
     
  3. 增加配置文件和数据库表结构

    spring:
      datasource:
        druid:
          driver-class-name: com.mysql.cj.jdbc.Driver
          url: jdbc:mysql://localhost:3306/zcct-user?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
          username: root
          password: zclvct
          # 初始连接数
          initial-size: 5
          # 最小连接数
          min-idle: 10
          # 最大连接数
          max-active: 20
          # 获取连接超时时间
          max-wait: 5000
          # 连接有效性检测时间
          time-between-eviction-runs-millis: 60000
          # 连接在池中最小生存的时间
          min-evictable-idle-time-millis: 300000
          # 连接在池中最大生存的时间
          max-evictable-idle-time-millis: 900000
          test-while-idle: true
          test-on-borrow: false
          test-on-return: false
          # 检测连接是否有效
          validation-query: select 1
          # 配置监控统计
          webStatFilter:
            enabled: true
          stat-view-servlet:
            enabled: true
            url-pattern: /druid
        @PostConstruct
        public void init() {
            Key key = generalKey();
            jwtParser = Jwts.parserBuilder()
                    .setSigningKey(key)
                    .build();
            jwtBuilder = Jwts.builder()
                    .signWith(key, SignatureAlgorithm.HS512);
        }
    
        
        public static Key generalKey() {
            byte[] encodedKey = Base64.decodeBase64(BASE64_SECRET);
            Key key = Keys.hmacShaKeyFor(encodedKey);
            return key;
        }
    
        
        public String createToken(String userId, JwtUser jwtUser) {
            String token = jwtBuilder
                    // 加入ID确保生成的 Token 都不一致
                    .setId(IdUtil.simpleUUID())
                    .claim(AUTHORITIES_KEY, userId)
                    .setSubject(userId)
                    .compact();
            redisUtil.set("login:"+userId,jwtUser,1L, TimeUnit.HOURS);
            return token;
        }
    
        
        public void parseJwt(String token) {
            if (StringUtils.hasText(token) && token.startsWith("Bearer ")) {
                // 去掉令牌前缀
                token = token.replace("Bearer ", "");
            } else {
                log.debug("非法Token:{}", token);
            }
    
            Claims claims = jwtParser.parseClaimsJws(token).getBody();
            String userId = claims.getSubject();
            JwtUser jwtUser = (JwtUser)redisUtil.get("login:" + userId);
            if(ObjectUtil.isEmpty(jwtUser)) {
                throw new RuntimeException("请求未认证");
            }
            Object authoritiesStr = claims.get(AUTHORITIES_KEY);
            Collection authorities =
                    ObjectUtil.isNotEmpty(authoritiesStr) ?
                            Arrays.stream(authoritiesStr.toString().split(","))
                                    .map(SimpleGrantedAuthority::new)
                                    .collect(Collectors.toList()) : Collections.emptyList();
    
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(jwtUser,"",authorities);
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        }
    }
    
    
    登录流程实现

    用户登录流程图

    登录接口实现
     public Map login(LoginUser loginUser) {
            String username = loginUser.getUsername();
            String password = loginUser.getPassword();
            // 生成一个 AuthenticationToken
            UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username,password);
            // 使用  authenticationManager 进行认证
            Authentication authentication = authenticationManagerBuilder.getObject().authenticate(token);
            JwtUser jwtUser = (JwtUser)authentication.getPrincipal();
            Long userId = jwtUser.getUser().getUserId();
            String jwtToken = jwtTokenProvider.createToken(String.valueOf(userId),jwtUser);
    
            Map authInfo = new HashMap(2) {{
                put("token", "Brear " + jwtToken);
                put("user", userId);
            }};
            return authInfo;
        }
    

    在调用 authenticationManagerBuilder.getObject().authenticate(token); 时, 会使用UserDetailsService加载用户信息进行验证,在这里需要增加自己的查询用户信息逻辑,并生成返回前端的token

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            User user = userService.findByUserName(username);
            if (user == null) {
                throw new BadRequestException("账号不存在");
            }
    
            if(user.getStatus() == 1) {
                throw new BadRequestException("账号已停用");
            }
            List authorities = roleService.getGrantedAuthorities(user);
            Set roleKeys = roleService.getRoleKeys(user);
            return new JwtUser(user,roleKeys,authorities);
        }
    
    授权流程

    1. 根据上传的token解析,设置用户相关信息到 SecurityContext
    2. 利用aop 获取请求注解信息根据注解调用SecurityUtils进行验证
    代码实现

    创建相关注解

    编写通知

    @Aspect
    @Component
    public class AuthorizeAspect {
    
        
        @Pointcut("@annotation(com.zcct.security.demo.security.annotation.RequireRules) " +
                "|| @annotation(com.zcct.security.demo.security.annotation.RequiresLogin) " +
                "|| @annotation(com.zcct.security.demo.security.annotation.RequiresPermissions) " +
                "|| @annotation(com.zcct.security.demo.security.annotation.RequireAnonymous)")
        public void pointcut() {
            // 该方法无方法体,主要为了让同类中其他方法使用此切入点
        }
    
        @Around("pointcut()")
        public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
    
    
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            authorize(method);
    
            Object obj = joinPoint.proceed();
            return obj;
        }
    
        private void authorize(Method method) {
            RequireRules requireRules = method.getAnnotation(RequireRules.class);
            if(ObjectUtil.isNotEmpty(requireRules)) {
                SecurityUtils.checkRules(requireRules.value());
            }
            RequiresLogin requiresLogin = method.getAnnotation(RequiresLogin.class);
            if(ObjectUtil.isNotEmpty(requiresLogin)) {
                SecurityUtils.checkLogin();
            }
            RequiresPermissions requiresPermissions = method.getAnnotation(RequiresPermissions.class);
            if(ObjectUtil.isNotEmpty(requiresPermissions)) {
                SecurityUtils.checkPermissions(requiresPermissions.value());
            }
            RequireAnonymous requireAnonymous = method.getAnnotation(RequireAnonymous.class);
            if(ObjectUtil.isNotEmpty(requireAnonymous)) {
                SecurityUtils.checkAnonymous();
            }
        }
    }
    

    增加工具类

    public class SecurityUtils {
    
        
        public static JwtUser getCurrentUser() {
            UserDetailsService userDetailsService = SpringUtil.getBean(UserDetailsService.class);
            return (JwtUser)userDetailsService.loadUserByUsername(getCurrentUsername());
        }
    
        
        public static String getCurrentUsername() {
    
            final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            if (authentication == null) {
                throw new BadRequestException(HttpStatus.FORBIDDEN,"当前登录状态过期");
            }
            if (authentication.getPrincipal() instanceof UserDetails) {
                UserDetails userDetails = (UserDetails) authentication.getPrincipal();
                return userDetails.getUsername();
            }
            throw new BadRequestException(HttpStatus.FORBIDDEN,"找不到当前登录的信息");
        }
    
        
        public static void checkRules(String[] rules) {
            Set ruleKeys = SecurityUtils.getCurrentUser().getRuleKeys();
            boolean hasAuthority = ruleKeys.contains("admin") || Arrays.stream(rules).anyMatch(ruleKeys::contains);
            if(!hasAuthority) {
                throw new BadRequestException(HttpStatus.UNAUTHORIZED,"没有权限");
            }
        }
    
        
        public static void checkLogin() {
            getCurrentUsername();
        }
    
        
        public static void checkPermissions(String[] permissions) {
            // 获取所有权限
            List allPermissions = SecurityUtils.getCurrentUser().getAuthorities().
                    stream().map(GrantedAuthority::getAuthority).
                    collect(Collectors.toList());
            boolean hasAuthority = allPermissions.contains("admin") || Arrays.stream(permissions).anyMatch(allPermissions::contains);
            if(!hasAuthority) {
                throw new BadRequestException(HttpStatus.UNAUTHORIZED,"没有权限");
            }
        }
    
        public static void checkAnonymous() {
            UserDetails userDetails = null;
            try{
                userDetails = getCurrentUser();
            }catch (Exception e){
    
            }
            if(ObjectUtil.isNotEmpty(userDetails)) {
                throw new BadRequestException(HttpStatus.UNAUTHORIZED,"不允许访问");
            }
        }
    }
    
    转载请注明:文章转载自 www.051e.com
    我们一直用心在做
    关于我们 文章归档 网站地图 联系我们

    版权所有 ©2023-2025 051e.com

    ICP备案号:京ICP备12030808号