Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

SpringSecurity整合JWT

Exrick edited this page Apr 29, 2019 · 2 revisions

JWT

JSON Web Token (JWT)是一个开放标准(RFC 7519),它定义了一种紧凑的、自包含的方式,用于作为JSON对象在各方之间安全地传输信息。该信息可以被验证和信任,因为它是数字签名的 官网:https://jwt.io

  • JSON Web Token由Header、Payload、Signature三部分组成,它们之间用圆点(.)连接。 一个典型的JWT看起来是这个样子的: xxxxx.yyyyy.zzzzz 分别对应:Header.Payload.Signature

Header

header典型的由两部分组成:token的类型("JWT")和算法名称(比如:HMAC SHA256或者RSA等等)。

例如:

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

用Base64对这个JSON编码就得到JWT的第一部分

Payload

JWT的第二部分是payload,通常在这部分存入交互信息,XBoot中存入了用户名和用户权限(避免每次请求再次读取用户权限)以及token失效时间。 例如:

{ "sub": "admin", "authorities": "["添加用户","ROLE_ADMIN"]", "exp": 1555554537 } 对payload进行Base64编码就得到JWT的第二部分

注意:签名并不是加密,任何人都能看到JWT里的内容,除非它们是加密的,因此请勿放置明文敏感信息至JWT中

Signature

签名是用于验证消息在传递过程中有没有被更改,并且对于使用私钥签名的token,它还可以验证JWT的发送方是否为它所称的发送方。 例如: HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

JWT缺点:JWT是无法撤销的,除非是达到了设定的过期时间,且刷新Token机制麻烦。解决放案:XBoot配置使用Redis记录,30分钟内无请求自动失效,可随时管理token,详见代码

Spring Security整合

  • 添加依赖
<!-- Spring Security -->
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT -->
<dependency>
 <groupId>io.jsonwebtoken</groupId>
 <artifactId>jjwt</artifactId>
 <version>0.9.1</version>
</dependency>
  • Spring Security核心配置入口
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 ...
 @Autowired
 private AuthenticationSuccessHandler successHandler;
 @Autowired
 private AuthenticationFailHandler failHandler;
 @Autowired
 private RestAccessDeniedHandler accessDeniedHandler;
 @Override
 protected void configure(HttpSecurity http) throws Exception {
 ...
 registry.and()
 ...
 //成功处理类
 .successHandler(successHandler)
 //失败
 .failureHandler(failHandler)
 //关闭跨站请求防护
 .csrf().disable()
 //前后端分离采用JWT 不需要session
 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
 .and()
 //自定义权限拒绝处理类
 .exceptionHandling().accessDeniedHandler(accessDeniedHandler)
 .and()
 //添加JWT过滤器 除已配置的其它请求都需经过此过滤器
 .addFilter(new JWTAuthenticationFilter(authenticationManager()));
 }
}
  • JWT过滤器 认证处理类
public class JWTAuthenticationFilter extends BasicAuthenticationFilter {
 public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
 super(authenticationManager);
 }
 public JWTAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint) {
 super(authenticationManager, authenticationEntryPoint);
 }
 @Override
 protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
 String header = request.getHeader(SecurityConstant.HEADER);
 if(StrUtil.isBlank(header)){
 header = request.getParameter(SecurityConstant.HEADER);
 }
 Boolean notValid = StrUtil.isBlank(header) || (!tokenRedis && !header.startsWith(SecurityConstant.TOKEN_SPLIT));
 if (notValid) {
 chain.doFilter(request, response);
 return;
 }
 try {
 UsernamePasswordAuthenticationToken authentication = getAuthentication(header, response);
 SecurityContextHolder.getContext().setAuthentication(authentication);
 }catch (Exception e){
 e.toString();
 }
 chain.doFilter(request, response);
 }
 private UsernamePasswordAuthenticationToken getAuthentication(String header, HttpServletResponse response) {
 // 用户名
 String username = null;
 // 权限
 List<GrantedAuthority> authorities = new ArrayList<>();
 
 // JWT
 try {
 // 解析token
 Claims claims = Jwts.parser()
 .setSigningKey(SecurityConstant.JWT_SIGN_KEY)
 .parseClaimsJws(header.replace(SecurityConstant.TOKEN_SPLIT, ""))
 .getBody();
 // 获取用户名
 username = claims.getSubject();
 String authority = claims.get(SecurityConstant.AUTHORITIES).toString();
 if(StrUtil.isNotBlank(authority)){
 List<String> list = new Gson().fromJson(authority, new TypeToken<List<String>>(){}.getType());
 for(String ga : list){
 authorities.add(new SimpleGrantedAuthority(ga));
 }
 }
 } catch (ExpiredJwtException e) {
 ResponseUtil.out(response, ResponseUtil.resultMap(false,401,"登录已失效,请重新登录"));
 } catch (Exception e){
 log.error(e.toString());
 ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"解析token错误"));
 }
 if(StrUtil.isNotBlank(username)) {
 // 踩坑提醒 此处password不能为null
 User principal = new User(username, "", authorities);
 return new UsernamePasswordAuthenticationToken(principal, null, authorities);
 }
 return null;
 }
}
  • 成功处理类
@Slf4j
@Component
public class AuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
 @Value("${xboot.tokenExpireTime}")
 private Integer tokenExpireTime;
 @Value("${xboot.saveLoginTime}")
 private Integer saveLoginTime;
 @Override
 @SystemLog(description = "登录系统", type = LogType.LOGIN)
 public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
 //用户选择保存登录状态几天
 String saveLogin = request.getParameter(SecurityConstant.SAVE_LOGIN);
 ...
 String username = ((UserDetails)authentication.getPrincipal()).getUsername();
 List<GrantedAuthority> authorities = (List<GrantedAuthority>) ((UserDetails)authentication.getPrincipal()).getAuthorities();
 List<String> list = new ArrayList<>();
 for(GrantedAuthority g : authorities){
 list.add(g.getAuthority());
 }
 // 登陆成功生成token
 String token = SecurityConstant.TOKEN_SPLIT + Jwts.builder()
 //主题 放入用户名
 .setSubject(username)
 //自定义属性 放入用户拥有请求权限
 .claim(SecurityConstant.AUTHORITIES, new Gson().toJson(list))
 //失效时间
 .setExpiration(new Date(System.currentTimeMillis() + tokenExpireTime * 60 * 1000))
 //签名算法和密钥
 .signWith(SignatureAlgorithm.HS512, SecurityConstant.JWT_SIGN_KEY)
 .compact();
 ResponseUtil.out(response, ResponseUtil.resultMap(true,200,"登录成功", token));
 }
}
  • 失败处理类
@Slf4j
@Component
public class AuthenticationFailHandler extends SimpleUrlAuthenticationFailureHandler {
 ...
 @Override
 public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
 ...
 if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
 ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"用户名或密码错误"));
 } else if (e instanceof DisabledException) {
 ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"账户被禁用,请联系管理员"));
 } else if (e instanceof LoginFailLimitException){
 ResponseUtil.out(response, ResponseUtil.resultMap(false,500,((LoginFailLimitException) e).getMsg()));
 } else {
 ResponseUtil.out(response, ResponseUtil.resultMap(false,500,"登录失败,其他内部错误"));
 }
 }
}
  • 自定义权限拒绝处理类
@Component
public class RestAccessDeniedHandler implements AccessDeniedHandler {
 @Override
 public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)
 throws IOException, ServletException {
 ResponseUtil.out(response, ResponseUtil.resultMap(false,403,"抱歉,您没有访问权限"));
 }
}

Clone this wiki locally

AltStyle によって変換されたページ (->オリジナル) /