JWT的学习与使用

JWT介绍

JWT是Json Web Token的缩写。它是基于RFC 7519 标准定义的一种可以安全传输的 小巧 和 自包含 的JSON对象。由于数据使用了数字签名,所以是可信任和安全的。

JWT的结构

JWT使用 . 分隔成三部分:

  • Header 头部,包含了两部分:token类型(jwt)和采用的加密算法(HMAC SHA256或RSA)
  • Payload 负载,包含了claim,claim是一些实体(通常指的是用户信息)
  • Signature 签名,由header(base64加密后)、payload (base64加密后)、secret三部分组成
    ⚠️:由于数据声明(Claim)是公开的,千万不要把密码等敏感字段放进去

Header例子:
此json将被Base64Url编码(相等于明文)以形成JWT的第一部分

{
  'typ': 'JWT',
  'alg': 'HS256'
}

Payload例子:
对有效负载进行Base64Url编码,以形成JWT的第二部分

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

Signature例子
签名用于验证消息在此过程中没有更改,并且对于使用私钥进行签名的令牌,它还可以验证JWT的发送者是它所说的真实身份

HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)

JWT的具体使用

1)pom.xml文件中添加相关依赖


    
    io.jsonwebtoken
    jjwt
    0.9.1

2)JwtAuthenticationTokenFilter类

public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    private final static Logger logger = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);

    @Resource
    private UserDetailsService userDetailsService;
    @Resource
    private JwtTokenUtils jwtTokenUtils;
    @Resource
    private UserService userService;

    private String tokenHeader = "Authorization";

    private String tokenHead = "Bearer ";

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        System.out.println("进入到 JWT Filter的doFilterInternal方法中");
        //先从url中取token
        String authToken = request.getParameter("token");
        String authHeader = request.getHeader(this.tokenHeader);
        if (StringUtils.isNotBlank(authHeader) && authHeader.startsWith(tokenHead)) {
            //如果header中存在token,则覆盖掉url中的token
            authToken = authHeader.substring(tokenHead.length()); // "Bearer "之后的内容
        }

        if (StringUtils.isNotBlank(authToken)) {
            String username = jwtTokenUtils.getUsernameFromToken(authToken);

            logger.info("checking authentication {}", username);
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                //从已有的user缓存中取了出user信息
                User user = userService.findUserByUsername(username);

                //检查token是否有效
                if (jwtTokenUtils.validateToken(authToken, user)) {
                    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(user, null, user.getAuthorities());

                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                    //设置用户登录状态
                    logger.info("authenticated user {}, setting security context",username);
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
        }
        chain.doFilter(request, response);
    }
}

3)application.yml

config:
  jwt:
    # 加密密钥
    secret: iwqjhda8232bjgh432[cicada-smile]
    # token有效时长
    expire: 3600

4)JwtTokenUtils类

@Component
public class JwtTokenUtils {
    private static final String CLAIM_KEY_USERNAME = "sub";
    private static final String CLAIM_KEY_ID = "id";
    private static final String CLAIM_KEY_CREATED = "created";
    private static final String CLAIM_KEY_ROLES = "roles";

    @Value("${config.jwt.secret}")
    private String secret;

    @Value("${config.jwt.expire}")
    private int expiration; //过期时长,单位为秒,可以通过配置写入

    public String getUsernameFromToken(String token) {
        String username;
        try {
            username =getClaimsFromToken(token).getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = new Date((Long) claims.get(CLAIM_KEY_CREATED));
        } catch (Exception e) {
            created = null;
        }
        return created;
    }

    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    private Date generateExpirationDate() {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    public String generateToken(User userDetails) {
        Map claims = new HashMap<>();
        claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
        claims.put(CLAIM_KEY_CREATED, new Date());
        claims.put(CLAIM_KEY_ID, userDetails.getId());
        claims.put(CLAIM_KEY_ROLES, userDetails.getAuthorities());
        return generateToken(claims);
    }

    public String generateToken(Map claims) {
        return Jwts.builder()
                .setClaims(claims)
                .setExpiration(generateExpirationDate())
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    public Boolean canTokenBeRefreshed(String token) {
        return !isTokenExpired(token);
    }

    public String refreshToken(String token) {
        String refreshedToken;
        try {
            final Claims claims = getClaimsFromToken(token);
            claims.put(CLAIM_KEY_CREATED, new Date());
            refreshedToken = generateToken(claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    public Boolean validateToken(String token, UserDetails userDetails) {
        User user = (User) userDetails;
        final String username = getUsernameFromToken(token);
        final Date created = getCreatedDateFromToken(token);
        return (
                username.equals(user.getUsername())
                        && isTokenExpired(token)==false);
    }
}

5)MySecurityConfig配置文件

@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    MyUserDetailService myUserDetailService;


    //Control+O 打开重写方法
    //定制请求的授权规则
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        System.out.println("进入到 MySecurityConfig的configure 方法中");
        //super.configure(http);
        /*Spring Security 的默认构造器:
        通过调用authorizeRequests()和 anyRequest().authenticated()就会要求所有进入应用的HTTP请求都要进行认证
        http
            .authorizeRequests()
                .anyRequest().authenticated()
                .and()
            .formLogin().and()
            .httpBasic(); //弹出一个输入用户名、密码的登录框

            ////////////////
             “/shop/hello” 和 “/shop/order” 这两个路径必须进过认证,并且 “/shop/order” 必须是 post 请求的方式.
             对于其他的请求,我们都是 .anyRequest().permitAll() ;都放行.
             http.authorizeRequests()
                .antMatchers("/shop/hello").authenticated()
                .antMatchers(HttpMethod.POST,"/shop/order").authenticated()
                .anyRequest().permitAll();

             antMatchers()方法所使用的路径可能会包括Ant风格的通配符,而regexMatchers()方法则能够接受正则表达式来定义请求路径。
         */
        // 基于token,所以不需要session
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
        // /**代表所有的请求
        http.authorizeRequests()//方法表示开启了认证规则配置;定义哪些url需要保护,哪些url不需要保护;
                .antMatchers("/api/**").permitAll()//定义不需要认证就可以访问
//                .antMatchers("/session/**").permitAll()//定义不需要认证就可以访问
//                .antMatchers("/component/**").hasAuthority("ROLE_ADMIN")
////                .antMatchers("/home/**").hasAnyAuthority("ROLE_DEV_APPLICATION","ROLE_ADMIN","ROLE_DEV_TERMINAL_IOS")
                .anyRequest().authenticated();////其他的路径都是登录后即可访问
//                .and().formLogin().loginPage("/")
//
//                //在successHandler中,使用response返回登录成功的json即可,切记不可以使用defaultSuccessUrl,defaultSuccessUrl是只登录成功后重定向的页面,failureHandler也是由于相同的原因不使用failureUrl。
//                .loginProcessingUrl("/login").successHandler(
//                        new AuthenticationSuccessHandler(){
//                            @Override
//                            public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
//                                System.out.println(authentication.getDetails());
//                                httpServletResponse.setContentType("application/json;charset=utf-8");
//                                PrintWriter out = httpServletResponse.getWriter();
//                                out.write("{\"status\":\"success\",\"msg\":\"登录成功\"}");
//                                out.flush();
//                                out.close();
//                            }
//                }).failureHandler(
//                        new AuthenticationFailureHandler() {
//                            @Override
//                            public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
//                                httpServletResponse.setContentType("application/json;charset=utf-8");
//                                PrintWriter out = httpServletResponse.getWriter();
//                                out.write("{\"status\":\"failed\",\"msg\":\"登录失败\"}");
//                                out.flush();
//                                out.close();
//                            }
//                });

                //http.logout()开启自动配置的注销功能
                //1) 访问/logout 表示用户注销,清空session
                //2) 注销成功会返回/login?logout 页面
                //3) logoutSuccessfulUrl 改变2)的设置
                http.logout().logoutSuccessUrl("/login");

                http.sessionManagement().invalidSessionUrl("/login");

                http.rememberMe().rememberMeParameter("remember");
//                .usernameParameter("username").passwordParameter("password").defaultSuccessUrl("/");////定义当需要用户登录时候,转到的登录页面
//                http.headers().frameOptions().disable();


//                .antMatchers("/level2/**").hasRole("VIP2")
//                .antMatchers("/level3/**").hasRole("VIP3");

        //开启自动配置的登录功能。如果没有登录,没有权限就会来到登录页面
        //1:/login来到登录页
        //2:重定向/login?error表示登录失败
        //3:更多详细规定
        //http.formLogin().defaultSuccessUrl("/user/login.html");

        /* iframe */
	    http.headers().frameOptions().sameOrigin(); // 运行同一个域名中的任何请求
        http.csrf().disable(); // 默认是启用的,需要禁用CSRF保护(当不使用cookie时可以禁用csrf)
        http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);
        // 禁用缓存
        http.headers().cacheControl();
    }

    //定制请求的认证规则
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        System.out.println("进入到 configureGlobal 方法中");
        System.out.println("auth.userDetailsService(myUserDetailService):" + auth.userDetailsService(myUserDetailService));
    //1)获取内存中的用户名和密码
//        auth.inMemoryAuthentication().passwordEncoder(new BCryptPasswordEncoder())
////                .withUser("1").password(new BCryptPasswordEncoder().encode("1")).roles("USER");
//                .withUser("1").password(new BCryptPasswordEncoder().encode("1")).authorities("ROLE_TEST");

//        auth.authenticationProvider(authenticationProvider());
//        auth.userDetailsService(myUserDetailService).passwordEncoder(passwordEncoder());

    //2)获取数据库中的用户名和密码
        auth.userDetailsService(myUserDetailService).passwordEncoder(new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
            }

            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return s.equals(charSequence.toString());
            }
        });
    }

    /*
     通过AuthenticationProvider方式获取
     */
    @Bean
    public DaoAuthenticationProvider authenticationProvider() {
        System.out.println("进入到 authenticationProvider 方法中");
        DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
        authProvider.setUserDetailsService(myUserDetailService);
        authProvider.setPasswordEncoder(passwordEncoder());
        return authProvider;
    }

    /**
     * 密码生成策略
     * @return
     */
    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        System.out.println("进入到 passwordEncoder 方法中");
        return new BCryptPasswordEncoder();
    }

    @Bean
    public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
        System.out.println("进入到 authenticationTokenFilterBean 方法中");
        return new JwtAuthenticationTokenFilter();
    }
}

你可能感兴趣的:(SpringSecurity,jwt)