SpringSecurity - 整合JWT使用 Token 认证授权
Posted 小毕超
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了SpringSecurity - 整合JWT使用 Token 认证授权相关的知识,希望对你有一定的参考价值。
一、SpringSecurity
前面讲解了SpringSecurity的动态认证和动态权限角色,我们都知道在现在大多都是微服务前后端分离的模式开发,前面讲的还是基于Session的,本篇我们整合JWT实现使用Token认证授权。
上篇文章地址:https://blog.csdn.net/qq_43692950/article/details/122394611
在开始前需要了解JWT,如果不了解,可以先看下下面这篇我的博客:
二、SpringSecurity 整合JWT使用 Token 认证授权
本篇文章还是接着上篇文章进行讲解,数据库还是使用上两篇文章中创建的数据库:
由于我们要使用JWT生成Token和存储一些信息,所以先引入JWT的依赖:
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.6.0</version>
</dependency>
编写JwtTool
工具:
@Data
@Component
public class JwtTool
private String key = "com.bxc";
private long overtime = 1000 * 60 * 60;
public String CreateToken(String userid, String username, List<String> roles)
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
JwtBuilder builder = Jwts.builder()
.setId(userid)
.setSubject(username)
.setIssuedAt(now)
.signWith(SignatureAlgorithm.HS256, key)
.claim("roles", roles);
if (overtime > 0)
builder.setExpiration(new Date(nowMillis + overtime));
return builder.compact();
public boolean VerityToken(String token)
try
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
if (claims != null)
return true;
catch (Exception e)
e.printStackTrace();
return false;
public String getUserid(String token)
try
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
if (claims != null)
return claims.getId();
catch (Exception e)
e.printStackTrace();
return null;
public String getUserName(String token)
try
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
if (claims != null)
return claims.getSubject();
catch (Exception e)
e.printStackTrace();
return null;
public List<String> getUserRoles(String token)
try
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
if (claims != null)
return (List<String>) claims.get("roles");
catch (Exception e)
e.printStackTrace();
return null;
public String getClaims(String token, String param)
try
Claims claims = Jwts.parser()
.setSigningKey(key)
.parseClaimsJws(token)
.getBody();
if (claims != null)
return claims.get(param).toString();
catch (Exception e)
e.printStackTrace();
return null;
使用CreateToken
就可以生成下面这种字符串:
eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiI4Iiwic3ViIjoiYWRtaW4iLCJpYXQiOjE2NDE3Mjg5NzgsInJvbGVzIjpbIlJPTEVfYWRtaW4iXSwiZXhwIjoxNjQxNzMyNTc4fQ.qI_pXiwm2IzZNRdyKvTRuSj0JxiPHepPOXg_u6AAE88
我们就使用类似上面这串做我们的Token。
既然使用Token了,肯定大多都是采用前后端分离架构,一般都是采用JSON进行交互的,但细心的会发现,前面的登录都是一个form表单的形式,所以第一步我们先把登录换成JSON的形式。
下面就需要重写自己的登录过滤器,需要实现UsernamePasswordAuthenticationFilter
接口,其中attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
表示获取用户用户名密码的入口,我们可以在这里自定义接受用户名密码,比如以json形式接受,然后再传递给Security
,然后Security
下面就会去调用UserDetailsService
做用户名和密码的正确性验证,如果用户名密码正确那就是登录成功,就会触发该实现下的successfulAuthentication
方法,否则就是unsuccessfulAuthentication
方法,我们可以在相应的方法中编写相应的提示返回给客户端:
public class LoginAuthenticationFilter extends UsernamePasswordAuthenticationFilter
private JwtTool jwtTool;
public LoginAuthenticationFilter(AuthenticationManager authenticationManager, JwtTool jwtTool)
this.setAuthenticationManager(authenticationManager);
this.jwtTool = jwtTool;
// this.setPostOnly(false);
// this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/login","POST"));
@SneakyThrows
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException
response.setContentType("text/json;charset=utf-8");
if (request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE) || request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE))
BufferedReader br = null;
try
br = new BufferedReader(new InputStreamReader(request.getInputStream(), "utf-8"));
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null)
sb.append(line);
JSONObject json = JSONObject.parseObject(sb.toString());
System.out.println(json.toString());
String username = json.getString("username");
String password = json.getString("password");
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
this.setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
catch (IOException e)
e.printStackTrace();
response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(400).message("参数错误!").build()));
else
return super.attemptAuthentication(request, response);
response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(400).message("参数错误!").build()));
return null;
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
FilterChain chain, Authentication authentication) throws IOException, ServletException
UserEntity user = (UserEntity) authentication.getPrincipal();
String username = user.getUsername();
List<GrantedAuthority> authorities = (List<GrantedAuthority>) user.getAuthorities();
List<String> roles = authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
String token = jwtTool.CreateToken(String.valueOf(user.getId()), username, roles);
response.setContentType("text/json;charset=utf-8");
Map<String, Object> map = new HashMap<>();
map.put("username", username);
map.put("role", roles);
map.put("token", token);
response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().data(map).build()));
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException
response.setContentType("text/json;charset=utf-8");
if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException)
response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(400).message("用户名或密码错误!").build()));
else if (e instanceof DisabledException)
response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(400).message("账户被禁用,请联系管理员!").build()));
else
response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(400).message("用户名或密码错误!").build()));
登录成功后,我们使用Jwt生成了一串Token并返还给用户,以后的所有请求都需要携带该Token,但Security
默认的是从Session中获取用户信息,显然也不符合我们的要求,所以下面我们要重写自己的Token过滤器。
需要实现BasicAuthenticationFilter
接口,我们只需在doFilterInternal
中做自己的逻辑即可,如果全部OK就放行该过滤器即可:
@Slf4j
public class JWTAuthenticationFilter extends BasicAuthenticationFilter
private JwtTool jwtTool;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager, JwtTool jwtTool)
super(authenticationManager);
this.jwtTool = jwtTool;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager, AuthenticationEntryPoint authenticationEntryPoint)
super(authenticationManager, authenticationEntryPoint);
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException
response.setContentType("text/json;charset=utf-8");
String token = request.getHeader("token");
if (StringUtils.isEmpty(token))
response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(401).message("登录失效!").build()));
return;
boolean isold = jwtTool.VerityToken(token);
if (!isold)
response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(401).message("登录失效!").build()));
return;
String username = jwtTool.getUserName(token);
if (StringUtils.isEmpty(username))
response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(401).message("登录失效!").build()));
return;
List<String> roles = jwtTool.getUserRoles(token);
if (roles.isEmpty())
response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(403).message("权限不足!").build()));
return;
List<GrantedAuthority> authorities = roles.stream().map(r -> new SimpleGrantedAuthority(r)).collect(Collectors.toList());
UserEntity principal = new UserEntity();
principal.setUsername(username);
try
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(principal, null, authorities);
SecurityContextHolder.getContext().setAuthentication(authentication);
String userid = jwtTool.getUserid(token);
request.setAttribute("userid", userid);
request.setAttribute("username", username);
// request.setAttribute("role", role);
chain.doFilter(request, response);
catch (Exception e)
e.printStackTrace();
response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(401).message("登录失效!").build()));
上面我们重写了接受用户名密码,和校验Token的过滤器,显然已经符合我们前后端分离架构,但是还有一个就是无权限的返回,在上两篇就可以看出,无权限是返回的403错误,显然也不符合,应该要修改为JSON的返回。
我们可以实现AccessDeniedHandler
这个接口,来做无权限自定义的返回:
@Component
public class AuthenticationAccessDeniedHandler implements AccessDeniedHandler
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException)
throws IOException, ServletException
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(JSON.toJSONString(ResponseTemplate.builder().code(403).message("权限不足!").build()));
最后修改WebSecurityConfig
,将上面的过滤器添加到Security
中,替换到默认的过滤器:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter
@Autowired
UserService userService;
@Autowired
CustomAccessDecisionManager customAccessDecisionManager;
@Autowired
CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;
@Autowired
AuthenticationAccessDeniedHandler authenticationAccessDeniedHandler;
@Autowired
JwtTool jwtTool;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception
auth.userDetailsService(userService).passwordEncoder(password());
@Bean
PasswordEncoder password()
return new BCryptPasswordEncoder();
@Override
protected void configure(HttpSecurity http) throws Exception
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>()
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o)
o.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
o.setAccessDecisionManager(customAccessDecisionManager);
return o;
)
.antMatchers("/**").fullyAuthenticated()
.and()
.formLogin()
.loginProcessingUrl("/login")
.permitAll()
.and()
.exceptionHandling()
.accessDeniedHandler(authenticationAccessDeniedHandler)
.and()
.addFilter(new JWTAuthenticationFilter(authenticationManager(), jwtTool))
.addFilter(new LoginAuthenticationFilter(authenticationManager(),jwtTool))
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
@Override
public void configure(WebSecurity web) throws Exception
web.ignoring().antMatchers("/register/**");
@Bean
RoleHierarchy roleHierarchy()
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
String hierarchy = "ROLE_admin > ROLE_user > ROLE_common";
roleHierarchy.setHierarchy(hierarchy);
return roleHierarchy;
相比于上一次的修改,这里就是通过addFilter
的方式添加我们的过滤器。
下面就可以启动项目,访问http://localhost:8080/admin/test
测试接口:
直接就是返回登录失效了,下面我们使用PostMan
登录:http://localhost:8080/login
可以看到这里我把权限也返回出来进行测试,表示该用户只能访问admin/**
,下面我们使用Token访问http://localhost:8080/admin/test
如果访问common/**
:
到这里就实现了使用Jwt Token的认证授权了。
喜欢的小伙伴可以关注我的个人微信公众号,获取更多学习资料!
以上是关于SpringSecurity - 整合JWT使用 Token 认证授权的主要内容,如果未能解决你的问题,请参考以下文章
从零玩转SpringSecurity+JWT整合前后端分离-从零玩转springsecurityjwt整合前后端分离