参考文章:
https://www.bilibili.com/video/BV15a411A7kP?p=1
https://www.bilibili.com/video/BV1d7411y7Uu
SpringSecurity在B站上讲的真的很糟糕,特别是与JWT的整合,看完视频后,依照老师的思路,自己整合了一下,现在讲自己的成果与大家分享,无需使用SpringCloud,只用了SpringBoot即可!在这里也推荐一下第二个b站的视频,他讲的SpringSecurity会稍微好一点,对代码也有会说明。
在开始之间先说明一下需要用到几个SpringSecurity的类
拦截登录请求并获取用户输入的账号和密码,然后把账号密码封装到 UsernamePasswordAuthenticationToken
(未受信任的认证凭据)中,然后将 “认证凭据" 交给 我们配置 的AuthenticationManager
去作认证
理解了上述流程后,我们可以做以下事情:
详见:https://felord.cn/usernamePasswordAuthenticationFilter.html
认证管理器。WebSecurityConfigurerAdapter
(配置SpringSecurity的类)中的void configure(AuthenticationManagerBuilder auth)
是配置AuthenticationManager
的地方,里面只有一个Authentication authenticate(Authentication authentication)
方法,他的作用是对 未受信任的认证凭据 做认证,认证成功则返回 授信状态的认证凭据 ,否则将抛出认证异常AuthenticationException
详见:https://www.jianshu.com/p/56e53d4ec1a9
认证对象,用于保存用户的信息。认证凭据 UsernamePasswordAuthenticationToken
实现了该接口。属性如下:
Boolean authenticated
:确定当前用户是否受信,为true
时受信,为false
时不受信。上述的返回 授信状态的认证凭据,就是将该值设置为true
。
Object principal
:主体。在未受信的情况是,这是用户名;在已受信的情况下,这是UserDetails
的实现类。
Object credentials
:在未受信的情况是,这是密码;在已受信的情况下,这是null
,所以这里也可以设置JWT
。
Collection authorities
:主体的权限集合。由AuthenticationManager
设置的,所以刚登录的时候,这里没有值;登录成功后主体中的权限会设置在这里。
Object details
:存储关于身份验证请求的其他详细信息。这些可能是IP地址、证书编号等。未使用返回null
详见:https://felord.cn/securityContext.html
这是一个工具类,用于设置、获取、清理SecurityContext
,这个SecurityContext
是存储Authentication
的容器,也就是说可以将受信用户存放到SecurityContext
中,允许该请求的后续访问。
详见:https://felord.cn/spring-security-securitycontext.html
负责处理 HTTP
头中显示的基本身份验证凭据。
重写void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
即可从 HTTP
头获取JWT
详见:https://felord.cn/spring-security-filters.html#3-24-BasicAuthenticationFilter
SecurityConfig.class
配置类
@Configuration
public class TokenWebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private TokenManager tokenManager;
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private UserDetailsService userDetailsService;
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.exceptionHandling()
.authenticationEntryPoint(new UnauthEnrtyPoint()) //无权限访问时,做出的处理
.and().authorizeRequests()
.antMatchers("/test/guest").permitAll() //所有请求都可以访问
.antMatchers("/test/user").hasRole("user") //只有角色为 user 的可以访问
.antMatchers("/test/admin").hasRole("admin") //只有角色为 admin 的可以访问
.anyRequest().authenticated()
.and().logout()
.logoutUrl("/test/logout") //设置退出路径
.addLogoutHandler(new TokenLogoutHandler(tokenManager,redisTemplate)) //退出时所做的逻辑
.and().addFilter(new TokenLoginFilter(tokenManager,redisTemplate,authenticationManager())) //登录时的过滤器
.addFilter(new TokenAuthFilter(tokenManager,redisTemplate,authenticationManager())) //验证JWT的过滤器
.httpBasic();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
}
TokenLoginFilter.class
登录认证
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
private AuthenticationManager authenticationManager;
public TokenLoginFilter(TokenManager tokenManager, RedisTemplate redisTemplate, AuthenticationManager authenticationManager) {
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
this.authenticationManager = authenticationManager;
//定制我们的登录请求URI和请求方式。
this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/test/login", "POST"));
}
/**
* 拦截登录。获取表单的用户名与密码
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
try {
//使用 请求体 传递登录参数,更加安全
LoginData user = new ObjectMapper().readValue(request.getInputStream(), LoginData.class);
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()));
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException();
}
}
/**
* 登录成功后调用的方法
* 返回token
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
SecurityUser user = (SecurityUser) authResult.getPrincipal();
//根据用户名生成token
String token = tokenManager.createToken(user.getUsername());
response.setHeader("token", token);
//将 用户名:角色 放入redis中
redisTemplate.opsForValue().set(user.getUsername(), user.getRole().getRole_name());
}
/**
* 登录失败后调用的方法
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.getWriter().print("login fail");
}
}
TokenAuthFilter.class
授权过滤,校验token的有效性
public class TokenAuthFilter extends BasicAuthenticationFilter {
private TokenManager tokenManager;
private RedisTemplate redisTemplate;
public TokenAuthFilter(TokenManager tokenManager, RedisTemplate redisTemplate, AuthenticationManager authenticationManager) {
super(authenticationManager);
this.tokenManager = tokenManager;
this.redisTemplate = redisTemplate;
}
/**
* 对HTTP请求头做处理
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//授权
UsernamePasswordAuthenticationToken authRequest = getAuthentication(request);
//授权失败
if (authRequest == null) {
chain.doFilter(request, response);
return;
}
//如果有授权,放到权限上下文(容器)中
SecurityContextHolder.getContext().setAuthentication(authRequest);
chain.doFilter(request, response);
}
/**
* 认证token是否合法,若合法,返回认证,否则返回null
*/
private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
//获取token
String token = request.getHeader("token");
if (!StringUtils.isEmpty(token)) {
//从token中获取username
String username = tokenManager.getUserInfoFromToken(token);
//redis中,根据username获取角色
String role_name = (String) redisTemplate.opsForValue().get(username);
Collection<GrantedAuthority> authorities = new ArrayList<>();
List<GrantedAuthority> list = AuthorityUtils.commaSeparatedStringToAuthorityList(role_name);
authorities.addAll(list);
if (!StringUtils.isEmpty(username)) {
return new UsernamePasswordAuthenticationToken(username, token, authorities);
}
}
return null;
}
}
https://github.com/Xavier-777/SpringSecurity-JWT