本文共 13138 字,大约阅读时间需要 43 分钟。
JWT是Json Web Token的缩写。它是基于RFC 7519 标准定义的一种可以安全传输的 小巧 和 自包含 的JSON对象。由于数据使用了数字签名,所以是可信任和安全的。
JWT使用 . 分隔成三部分:
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)
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类
@Componentpublic 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) { Mapclaims = 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配置文件
@EnableWebSecuritypublic 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(); }}
转载地址:http://zrtii.baihongyu.com/