1 .概述

  • 基于JWT 实现身份认证和授权,是当前流行的一种技术解决方案。
  • 使用这种方案,要求用户先发来用户名与密码,当用户名与密码校验通过时,服务器端返回一个JWT给客户端。
  • 客户端拿到JWT之后,将其放到HTTP 请求名为Authorization的Header 中,随后继请求一起发给服务端
  • 服务端先校验JWT的有效性,有效之后,开放资源给客户端访问。

2. Token

  • Token 是一种有着特定含义的短小的字符串
  • 在Web开发中,Token 通常由WebServer 端生成,“颁发”给客户端,这些Token通常保存由一些比较重要的信息,客户端在发出HTTP请求时,通常要求在HTTP Header 中放置这些Token供Server 端处理。
  • 为了保证安全性,Token 通常被编码和签名,并且拥有一个特定的“生存期”,过期的Token会被废弃。

3. 实战

(1) 添加项目依赖
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-api</artifactId>
            <version>0.11.5</version>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-impl</artifactId>
            <version>0.11.5</version>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt-jackson</artifactId>
            <version>0.11.5</version>
        </dependency>
(2) 定义常量
public interface SecurityConstants {

    public static final String JWT_KEY="这是我的一个私有密钥,用于生成JWT";
    //设置放在HTTP请求Header的名字
    public static final String JWT_HEADER="Authorization";
}
(3) 生成JWT
public class JWTUtils {
    public static String generateJWT(){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if(authentication != null){
            SecretKey secretKey = Keys.hmacShaKeyFor(SecurityConstants.JWT_KEY.getBytes(StandardCharsets.UTF_8));
             String jwt = Jwts.builder()
                     .setIssuer("XZ")
                     .setSubject("JWT Token")
                     .claim("username", authentication.getName())
                     .claim("authorities", fillAuthorities(authentication.getAuthorities()))
                     .setIssuedAt(new Date())
                     .setExpiration(new Date(new Date().getTime()+30000000))
                     .signWith(secretKey)
                     .compact();
             return jwt;
        }
        return "";
    }


    private static String fillAuthorities(Collection<? extends GrantedAuthority> collection){
        Set<String> authorities = new HashSet<>();
        for(GrantedAuthority authority : collection){
            authorities.add(authority.getAuthority());
        }
        return String.join(",",authorities);
    }
}

(4)在控制器中生成JWT
  • 在这里,生成JWT之后,将其放到HTTP响应的body中…
    @GetMapping("/token")
    public ResponseEntity<String> jwtToken(){
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if(authentication != null){
            return ResponseEntity.ok(JWTUtils.generateJWT());
        }else {
            return ResponseEntity.badRequest().body("无效的用户名与密码");
        }
    }

(5) 编写JWT校验代码
    //使用私钥校验JWT的有效性,有效,则返回true
    public static boolean  validateJWT(String jwt){
        try{
            SecretKey secretKey = Keys.hmacShaKeyFor(SecurityConstants.JWT_KEY.getBytes(StandardCharsets.UTF_8));
            //提取JWT中的信息
            Claims claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(jwt).getBody();
            String username = String.valueOf(claims.get("username"));
            String authorities = String.valueOf(claims.get("authorities"));
            //创建Spring Security所使用的usernamePasswordAuthenticationToken
            UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(username, null, AuthorityUtils.commaSeparatedStringToAuthorityList(authorities));
            //将认证结果保存到SecurityContext中
            SecurityContextHolder.getContext().setAuthentication(auth);
            return true;


        }catch (Exception e){
            return false;

        }
    }

(6).构建用于校验JWT的Filter
  • 由于往往很多服务都需要认证与授权之后才能访问,因此,将相关逻辑放到Filter 中是一个很好的选择
public class JWTTokenValidatorFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String jwt=request.getHeader(JWT_HEADER);
        if(jwt!=null){
            //如果校验失败
            if(!JWTUtils.validateJWT(jwt)){
                // throw new BadCredentialsException("无效的Token");
                System.out.println("无效的Token");
                //return;

            }
        }
        filterChain.doFilter(request, response);
    }


    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
        //公开的URL,不需要校验Token
        String url = request.getServletPath();
        return url.startsWith("/public") || url.equals("/register") || url.equals("/token");
    }
}
(7)将Filter 追加到过滤器链中

将Filter追加到过滤器链中

  • 先从Token 中提取信息,再进行Basic 认证
@Configuration
public class MyWebSecurityFConfig {
    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        //使用JWT认证,服务端无需保存会话消息
        http.sessionManagement(config->{
            config.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        });



        http.authorizeHttpRequests(
                requests->{

                    requests.requestMatchers("/register").permitAll();
                    requests.requestMatchers("/token/**").hasAnyAuthority("USER","ADMIN");
                    requests.requestMatchers("/public/**").hasAnyAuthority("USER","ADMIN");
                    requests.requestMatchers("/private/**").hasAuthority("ADMIN");


                }
        );
        //配置cors
        http.cors(AbstractHttpConfigurer::disable);
        http.csrf(AbstractHttpConfigurer::disable);
        http.formLogin(withDefaults());
        http.httpBasic(withDefaults());
        //在认证Filter完成之前,先校验Token的有效性
        http.addFilterBefore(new JWTTokenValidatorFilter(), BasicAuthenticationFilter.class);

        //在认证Filter完成之后使用自定义Filter 打印日志
        http.addFilterAfter(new MySecurityLogFilter(), BasicAuthenticationFilter.class);


        return http.build();
    }


  
    @Bean
    public PasswordEncoder myPasswordEncoder() {
        return new BCryptPasswordEncoder();
    };
}
`
``

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐