Spring-boot-Security: 基于Spring Boot整合的快速实现。
一、引入依赖架包
<!--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>
二、准备token认证过滤器、认证失败处理类、自定义退出处理类型、自定义用户认证逻辑
- 自定义token认证过滤器
认证流程
用户提交用户名、密码被SecurityFilterChain中的 UsernamePasswordAuthenticationFilter 过滤器获取到,封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。
然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证
认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除) Authentication 实例。
SecurityContextHolder 安全上下文容器将第3步填充了信息的 Authentication ,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个List 列表,存放多种认证方式,最终实际的认证工作是由AuthenticationProvider完成的。咱们知道web表单的对应的AuthenticationProvider实现类为DaoAuthenticationProvider,它的内部又维护着一个UserDetailsService负责UserDetails的获取。最终AuthenticationProvider将UserDetails填充至Authentication。
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private TokenService tokenService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
try{
//根据token获取登陆用户
LoginUser loginUser = tokenService.getLoginUser(request);
//判断是否有登陆
if (Objects.nonNull(loginUser) && Objects.isNull(SecurityUtils.getAuthentication())) {
//刷新token
tokenService.verifyToken(loginUser);
//通过UsernamePasswordAuthenticationToken进行用户认证,认证成功后,AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)实例
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//将用户详细信息塞入SecurityContextHolder安全上下文容器中
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
chain.doFilter(request, response);
}catch (CustomException e){
throw new CustomException(ErrorCodeEnum.ACCESS_TOKEN_TIME_OUT);
}
}
}
2.认证失败处理类
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint, Serializable {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
//根据业务逻辑编写对应的处理方法
}
}
3.自定义退出处理类型
@Configuration
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
@Autowired
private TokenService tokenService;
/**
* 退出处理
*
* @return
*/
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
//根据业务逻辑编写对应的处理方法
}
4.自定义用户认证逻辑
userDetailsService:用户主体管理服务。如果设置了这个属性,那说明有一个自己的UserDetailsService接口的实现,或者你可以把这个东东设置到全局域(例如GlobalAuthenticationManagerConfigurer)上去,当你设置了这个之后,那么refresh_token刷新令牌方式的授权类型流程中就会多包含一个检查步骤,来确保这个账号是否仍然有效。
@Service
public class UserDetailsServiceImpl implements UserDetailsService
{
private static final Logger log = LoggerFactory.getLogger(UserDetailsServiceImpl.class);
@Autowired
private SysUserService userService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
{
QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
wrapper.eq("userName",username);
SysUser user = userService.getOne(wrapper);
if (StringUtils.isNull(user))
{
log.info("登录用户:{} 不存在.", username);
throw new UsernameNotFoundException("登录用户:" + username + " 不存在");
}
else if ("2".equals(user.getUserStatus()))
{
log.info("登录用户:{} 已被停用.", username);
throw new BaseException("对不起,您的账号:" + username + " 已停用");
}
//返回的用户可以将菜单权限一起存入到user中返回进去后期可以用来配合@PreAuthorize进行权限校验
return user;
}
}
三、通过WebSecurityConfigurerAdapter自定义授权及安全拦截策略及身份认证接口
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
/**
* 跨域过滤器
*/
@Autowired
private CorsFilter corsFilter;
/**
* token认证过滤器
*/
@Autowired
private JwtAuthenticationTokenFilter authenticationTokenFilter;
/**
* 认证失败处理类
*/
@Autowired
private AuthenticationEntryPointImpl unauthorizedHandler;
/**
* 自定义退出处理类型
*/
@Autowired
private LogoutSuccessHandlerImpl logoutSuccessHandler;
/**
* 自定义用户认证逻辑
*/
@Autowired
private UserDetailsService userDetailsService;
/**
* 解决 无法直接注入 AuthenticationManager
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception
{
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// CSRF跨域检查禁用,因为不使用session
.csrf().disable()
// 认证失败处理类
.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
// 基于token,所以不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
// 打开请求认证授权
.authorizeRequests()
// 对于登录login 注册register 验证码captchaImage 允许匿名访问
.antMatchers("/login", "/register", "/captchaImage","/logout").anonymous()
.antMatchers("/common/download**").permitAll()
.antMatchers(
HttpMethod.GET,
"/",
"/*.html",
"/**/*.html",
"/**/*.css",
"/**/*.js",
"/profile/**"
).permitAll()
.antMatchers("/swagger-ui.html").anonymous()
.antMatchers("/swagger-resources/**").anonymous()
.antMatchers("/webjars/**").anonymous()
.antMatchers("/*/api-docs").anonymous()
.antMatchers("/druid/**").anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable();
//定义退出处理
httpSecurity.logout().logoutUrl("/logout").logoutSuccessHandler(logoutSuccessHandler);
// 添加JWT filter
httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
// 添加CORS filter
httpSecurity.addFilterBefore(corsFilter, JwtAuthenticationTokenFilter.class);
httpSecurity.addFilterBefore(corsFilter, LogoutFilter.class);
}
/**
* 身份认证接口
* userDetailsService 自定义注入UserDetailsService来管理系统的实体数据,重写loadUserByUsername来进行用户验证
* MsPasswordEncoder自定义加密解密处理类
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(new MsPasswordEncoder());
}
}
四、通过上下文获取用户信息
@RestController
@RequestMapping("/common")
public class LoginController {
@GetMapping("/getLoginUserByPrincipal")
public String getLoginUserByPrincipal(Principal principal){
return principal.getName();
}
@GetMapping(value = "/getLoginUserByAuthentication")
public String currentUserName(Authentication authentication) {
return authentication.getName();
}
@GetMapping(value = "/username")
public String currentUserNameSimple(HttpServletRequest request) {
Principal principal = request.getUserPrincipal();
return principal.getName();
}
@GetMapping("/getLoginUser")
public String getLoginUser(){
Principal principal = (Principal)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
return principal.