SpringBoot集成Security框架
1、核心配置详解
1)SecurityConfig(security配置):主要用来对Security整体进行配置,要继承WebSecurityConfigurerAdapter类,在configure方法中可以对跨域,接口请求资源放行,异常处理,过滤器进行配置。也可以在类中通过 @Bean注解注入所要使用的Bean到容器中进行使用,比如AuthenticationManager(用来对用户的认证)和PasswordEncoder(对密码进行加密)类。
2)JwtFilter(jwt过滤器):在用户名和密码过滤器(UsernamePasswordAuthenticationFilter(security自带的))之前对请求进行拦截,对请求携带的token进行拦截判断。JwtFilter类继承OncePerRequestFilter类并实现doFilterInternal方法,对请求进行过滤。
3)UserDetailsServiceImpl:需要实现UserDetailsService类,目的是用来完成用户在登录对数据库用户的查询,在登录方法中AuthenticationManager(认证管理器)会自动调用这个实现类进行查询。UserDetailsServiceImpl的返回结果应该是UserDetails的实现类,因此需要单独写一个类UserDetailImpl来实现UserDetails(用来封装用户信息)。
4)UserDetailImpl:用来对用户认证身份进行封装,包括权限、用户名,密码、账号过期锁定等等。用来把autenticate.getPrincipal()对象转换成UserDetailImpl的用户对象,这里之所以这样,是因为security内部是通过UserDetails实现的,我们实现了UserDetails这个类,所以可以用UserDetailImpl进行封装。
5)WebUtils(封装了统一的返回方法):用来对security发生异常时,对数据的返回
6)AccessDeniedHandlerImpl:访问拒绝的实现类,用来对访问拒绝的异常进行返回(主要是权限异常)。需要实现AccessDeniedHandler,并且实现其中的handle方法。这个方法中会调用WebUtils中的方法。
6)AuthenticationEntryPointImpl:处理认证异常的实现类,需要实现AuthenticationEntryPoint类,用来对认证过程中的异常信息进行处理并返回,需要实现commence方法,方法中会调用WebUtils中方法。
2、核心配置代码
1)SecurityConfig
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Resource
private JwtFilter jwtFilter;
@Resource
AuthenticationEntryPoint authenticationEntryPoint;
@Resource
AccessDeniedHandler accessDeniedHandler;
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
//关闭csrf
.csrf().disable()
//不通过Session获取SecurityContext
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
// 对于登录接口 允许匿名访问
//.antMatchers("/login").anonymous()
/**
* 放行swagger资源和登录资源
*/
//.antMatchers("/login","/doc.html","/webjars/**","/img.icons/**","/swagger-resources/**","/**","/v2/api-docs").permitAll()
//.anyRequest().authenticated();
// .antMatchers("/user/*").authenticated()
//jwt过滤器测试用,如果测试没有问题吧这里删除了
// .antMatchers("/link/getAllLink").authenticated()
// .antMatchers("/user/*").authenticated()
// 除上面外的所有请求全部不需要认证即可访问
.antMatchers("/api/user/login","/api/user/regist").permitAll()
.antMatchers("/api/user/**").authenticated()
.anyRequest().permitAll();
//配置异常处理器
http.exceptionHandling()
//认证异常
.authenticationEntryPoint(authenticationEntryPoint)
//权限异常
.accessDeniedHandler(accessDeniedHandler);
//关闭默认的退出登录功能
http.logout().disable();
//把jwtAuthenticationTokenFilter添加到SpringSecurity的过滤器链中
http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
//允许跨域
http.cors();
}
}
2)JwtFilter
@Slf4j
@Component
public class JwtFilter extends OncePerRequestFilter {
@Autowired
IUserService userService;
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
// String requestURI = httpServletRequest.getRequestURI();
// log.warn(requestURI);
String token = httpServletRequest.getHeader("token");
if(StrUtil.hasEmpty(token)){
//如果没有携带token,那么直接放行,这里需要对没有携带token的请求都放行,需要认证的请求可以在securityConfig中进行配置认证
filterChain.doFilter(httpServletRequest,httpServletResponse);
return;
}
Claims claims = null;
try {
//解析token
claims = jwtUtil.parseJWT(token);
}catch (Exception e){
//token异常
e.printStackTrace();
ResponseResult result = ResponseResult.fail(ResponseCode.TOKEN_ERROR.code,ResponseCode.TOKEN_ERROR.msg);
WebUtils.renderString(httpServletResponse, StrUtil.toString(result));
return;
}
//获取token中存的关于用户的信息
String userId = claims.getSubject();
User user = userService.getById(Long.valueOf(userId));
if(user==null){
ResponseResult result = ResponseResult.fail(ResponseCode.FAIL.code,"用户不存在");
WebUtils.renderString(httpServletResponse, JSONUtil.toJsonStr(result));
return;
}
//交给security进行管理
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(user,token,null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行过滤器
filterChain.doFilter(httpServletRequest,httpServletResponse);
}
}
3)UserDetailsServiceImpl
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Resource
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
//通过用户名查找用户
User user = userMapper.selectOne(queryWrapper.eq("user_name", username));
if(Objects.isNull(user)){
throw new UsernameNotFoundException("用户不存在");
}
//返回封装认证用户类
return new UserDetailImpl(user);
}
}
4)UserDetailImpl
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserDetailImpl implements UserDetails {
private User user;
//权限
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return user.getUserPass();
}
@Override
public String getUsername() {
return user.getUserName();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}
5)WebUtils
public class WebUtils
{
/**
* 将字符串渲染到客户端
*
* @param response 渲染对象
* @param string 待渲染的字符串
* @return null
*/
public static void renderString(HttpServletResponse response, String string) {
try
{
response.setStatus(200);
response.setContentType("application/json");
response.setCharacterEncoding("utf-8");
response.getWriter().print(string);
}
catch (IOException e)
{
e.printStackTrace();
}
}
public static void setDownLoadHeader(String filename, ServletContext context, HttpServletResponse response) throws UnsupportedEncodingException {
String mimeType = context.getMimeType(filename);//获取文件的mime类型
response.setHeader("content-type",mimeType);
String fname= URLEncoder.encode(filename,"UTF-8");
response.setHeader("Content-disposition","attachment; filename="+fname);
// response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
// response.setCharacterEncoding("utf-8");
}
}
6)AccessDeniedHandlerImpl
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
//打印下异常
accessDeniedException.printStackTrace();
ResponseResult result = ResponseResult.fail(ResponseCode.UNAUTHORI.code,ResponseCode.UNAUTHORI.msg);
//以json的形式相应给前端
WebUtils.renderString(response, JSONUtil.toJsonStr(result));
}
}
7)AuthenticationEntryPointImpl
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
//打印下异常
authException.printStackTrace();
//登录认证是根据不同的情况,提示不同的错误
ResponseResult result = null;
//BadCredentialsException密码错误的异常,坏的凭据
//InsufficientAuthenticationException,凭据不足
if(authException instanceof BadCredentialsException){
result = ResponseResult.fail(ResponseCode.FAIL.code,authException.getMessage());
}else if(authException instanceof InsufficientAuthenticationException){
result = ResponseResult.fail(ResponseCode.UNLOGIN.code,authException.getMessage());
}else{
result = ResponseResult.fail(ResponseCode.FAIL.code,"认证和授权出线问题");
}
//以json的形式相应给前端
WebUtils.renderString(response, JSONUtil.toJsonStr(result));
}
}
3、注意事项
在使用UsernamePasswordAuthenticationToken进行用户和密码认证时,需要调用AuthenticationManager认证管理器进行管理,在这个过程中一定不能忘记实现UserDetailsServiceImpl和UserDetailImpl类,不然会走默认的认证流程,这时候就查询不到用户数据,会报出Handler dispatch failed; nested exception is java.lang.StackOverflowError这个异常
4、自定义登录
@Service//这里是user的业务层,用的mybatisplus
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private AuthenticationManager authenticationManager;
@Override
public ResponseResult login(UserDto userDto) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDto.getUserName(),userDto.getUserPass());
Authentication authenticate = authenticationManager.authenticate(authenticationToken);
if(Objects.isNull(authenticate)){
// throw new BadCredentialsException("用户名或密码错误");
return ResponseResult.fail(ResponseCode.FAIL.code,"账号或密码错误");
}
UserDetailImpl userDetail = (UserDetailImpl)authenticate.getPrincipal();
String userId = userDetail.getUser().getUserId().toString();
String jwt = JwtUtil.createJWT(userId);
return ResponseResult.success(MapUtil.builder().put("token",jwt).build());
}
}
版权声明:本文为weixin_44357065原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。