Jwt的简介与使用

186 阅读4分钟

1. Jwt 简介

JSON Web Token (JWT)是一种基于 token 的认证方案。

随着技术的发展,分布式web应用的普及,通过session管理用户登录状态成本越来越高,因此慢慢发展成为token的方式做登录身份校验,然后通过token去取redis中的缓存的用户信息,随着之后jwt的出现,校验方式更加简单便捷化,无需通过redis缓存,而是直接根据token取出保存的用户信息,以及对token可用性校验,单点登录更为简单。

2. Jwt 的组成

JSON Web Tokens 由用点 ( .)分隔的三个部分组成,

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSAiOiJKb2huIERvZSIsImlhdCI6MTUxNjIzOTAyMn0. SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

分别是:

  • 标题 (header)
  • 载荷 (payload)
  • 签名 (signature)
    • 标题为第一部分,通常由两部分组成:令牌的类型,即 JWT,以及正在使用的签名算法
    • 载荷是第二部分,其中包含声明。声明是关于实体(通常是用户)和附加数据的声明。 共有三种类型的声 明:注册声明公共声明私有声明
    • 签名为第三部分,要创建签名部分,您必须获取编码的标头、编码的有效载荷、秘密,标头中指定的算法,并对其进行签名。

以 HMACSHA256为签名算法 为例简化为下面结构:

`

base64UrlEncode(header).
base64UrlEncode(payload).
HMACSHA256(
          base64UrlEncode(header) + "." +
          base64UrlEncode(payload),
          your-256-bit-secret
        ) `

3. Jwt 的使用——JJWT

jwt的框架:JJWT 其实就是简单创建使用jwt

JJWT是一个提供端到端的JWT创建和验证的Java库。 JJWT很容易使用和理解。它被设计成一个以建筑为中心的流畅界面,隐藏了它的大部分复杂性。

3.1 导入依赖

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>

3.2 创建JwtUtil 工具类

通过 JJwt 中的 Jwts,来创建解析 jwt

@Data
@Component
@ConfigurationProperties(prefix = "ljw.jwt")   //通过配置文件设置参数
public class JwtUtil {

    private long expire;
    private String secret;
    private String header;  // jwt 的名称

    // 生成jwt
    public String generateToken(String username){

        Date nowDate = new Date();
        Date expireDate = new Date(nowDate.getTime()+1000*expire);

        String jwt = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setSubject(username)    // 主体
                .setIssuedAt(nowDate)    // 创建时间
                .setExpiration(expireDate)         // 过期时间 
                .signWith(SignatureAlgorithm.HS256, secret)  // 指定签名生成算法,及密钥
                .compact();

        return jwt;
    }

    // 解析jwt
    public Claims getClaimsToken(String jwt){
       // 返回结果若不为空,则jwt 合法, 否则不合法
        try {
         return  Jwts.parser()
                    .setSigningKey(secret)  // 通过密钥解析
                    .parseClaimsJws(jwt)
                    .getBody();
        } catch (Exception e) {
            return null;
        }
    }


    // jwt 是否过期
    public boolean isTokenExpired(Claims claims){
        return  claims.getExpiration().before(new Date());
    }
}

application.yml 配置文件中

ljw:
  jwt:
    header: Authorization
    expire: 604800 #7天,秒单位
    secret: ji8n3439n439n43ld9ne9343fdfer49h
   

3.3 结合Spring Sevurity 使用



@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

     @Autowired
    JwtUtil jwtUtil;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        response.setContentType("application/json;charset=UTF-8");
        ServletOutputStream outputStream = response.getOutputStream();

        // 生成jwt 放入 请求头 header中
        String token = jwtUtil.generateToken(authentication.getName());
        response.setHeader(jwtUtil.getHeader(),token);

        Result result = Result.success("");

        outputStream.write(JSONUtil.toJsonStr(result).getBytes("UTF-8"));

        outputStream.flush();
        outputStream.close();
    }


}

public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
    @Autowired
    JwtUtil jwtUtil;

    @Autowired
    UserDetailServiceImpl userDetailService;

    @Autowired
    SysUserService sysUserService;

    public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
        super(authenticationManager);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {

        String token = request.getHeader(jwtUtil.getHeader());
        // token 为空则放行, 后面在进行 相关的请求权限判断
        //  若不为空 则进行身份验证
        if(StrUtil.isBlankOrUndefined(token)){
            chain.doFilter(request,response);
            return ;
        }

        Claims claim = jwtUtil.getClaimsToken(token);
             if(claim ==null){
                 throw  new JwtException("token 异常");
             }
             // 判断是否过期
             if(jwtUtil.isTokenExpired(claim)){
                 throw new JwtException("token 已过期");
             }

        String username=claim.getSubject();
        //获取用户的权限信息
        SysUser sysUser = sysUserService.getByUsername(username);

        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken
                = new UsernamePasswordAuthenticationToken(username,null,userDetailService.getUserAuthority(sysUser.getId()));

        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);

        chain.doFilter(request,response);

    }
}

@Configuration
@EnableWebSecurity     // 开启Security 的安全策略
@EnableGlobalMethodSecurity(prePostEnabled = true)  // 在post 请求前进行权限校验
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    LoginFailureHandler loginFailureHandler;
    @Autowired
    LoginSuccessHandler loginSuccessHandler;
    @Autowired
    CaptchaFilter captchaFilter;

    @Bean
    JwtAuthenticationFilter jwtAuthenticationFilter() throws Exception {
        JwtAuthenticationFilter jwtAuthenticationFilter
                 = new JwtAuthenticationFilter(authenticationManager());
        return jwtAuthenticationFilter;
    }

    @Autowired
    JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Autowired
    JwtAccessDeniedHandler jwtAccessDeniedHandler;

    @Bean
    BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    @Autowired
    UserDetailServiceImpl userDetailService;

    @Autowired
    JwtLogoutSuccessHander jwtLogoutSuccessHander;

    private  static  final  String[] URL_WHILELIST={
      "/login","/logout",
      "/captcha","/favicon.ico","/test/**"
    };

    protected void configure(HttpSecurity http) throws Exception {

       http.cors().and().csrf().disable()
               // 登录配置
        .formLogin()
                             // 登录成功或者失败后,对应进行回调
               .successHandler(loginSuccessHandler)
               .failureHandler(loginFailureHandler)

               // 退出登录 配置
         .and()
               .logout()
               .logoutSuccessHandler(jwtLogoutSuccessHander)

              // 禁用session
         .and()
                              //设置无状态的连接,即不创建session
               .sessionManagement()
               .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
             // 配置拦截规则
         .and()
                              // 除了 白名单的请求 放过,其他正常拦截
               .authorizeRequests()
               .antMatchers(URL_WHILELIST).permitAll()
               .anyRequest().authenticated()
             // 异常处理器
         .and()
               .exceptionHandling()
               .authenticationEntryPoint(jwtAuthenticationEntryPoint)       // 认证失败 异常处理入口
               .accessDeniedHandler(jwtAccessDeniedHandler)            // 配置 权限不足 处理器

             // 配置自定义的过滤器
          .and()
               .addFilter(jwtAuthenticationFilter())
               .addFilterBefore(captchaFilter, UsernamePasswordAuthenticationFilter.class)  // 登录验证码过滤器

        ;
    }


    /**
     * 配置
     * 将 userDetailService的实现类 注入到 security 中
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService);
    }
}