1、WebSecurityConfigurerAdapter
@Configuration
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final UserDetailsServiceImpl userDetailsService;
@Lazy
@Resource
private AccessDeniedHandlerImpl accessDeniedHandler;
@Lazy
@Resource
private AuthenticationEntryPointImpl authenticationEntryPoint;
private final PasswordEncoder passwordEncoder;
@Lazy
@Resource
private TokenVerificationFilter tokenVerificationFilter;
@Lazy
@Resource
private LogoutSuccessHandlerImpl logoutSuccessHandler;
public WebSecurityConfiguration(UserDetailsServiceImpl userDetailsService, PasswordEncoder passwordEncoder) {
this.userDetailsService = userDetailsService;
this.passwordEncoder = passwordEncoder;
}
@Override
@Bean
@ConditionalOnMissingBean(AuthenticationManager.class)
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.cors()
.and()
.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/healthy").permitAll()
.antMatchers("/doc.html", "/doc.html#/home", "/doc.html#/home/**", "/v2/**", "/webjars/**", "/swagger-resources").permitAll()
.antMatchers("/auth/login", "/auth/logout").permitAll()
.mvcMatchers("/manage/**").hasAuthority("admin")
.mvcMatchers("/manage/loginLog").hasAuthority("log_admin")
.anyRequest().authenticated()
.and()
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler)
.and()
.logout().logoutUrl("/auth/logout").logoutSuccessHandler(logoutSuccessHandler)
;
http.addFilterBefore(tokenVerificationFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
}
}
2、UserDetailsServiceImpl
@Component
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService{
@Resource
@Lazy
private AuthenticationManager authenticationManager;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
final UserDO userDO = iUserService.findBy(username);
if (Objects.isNull(userDO)) {
return null;
}
return UserInfoDetails.builder()
.id(userDO.getId())
.username(userDO.getUsername())
.photo(userDO.getPhoto())
.password(userDO.getPassword())
.fullName(userDO.getFullName())
.departmentDetails(userDO.getDepartmentDetails())
.authorities(userDO.getRoles.toArray(new String[0]))
.isAccountNonExpired(true)
.isAccountNonLocked(true)
.isCredentialsNonExpired(true)
.isEnabled(true).build();
}
public LoginUserDTO login(LoginParam loginParam, HttpServletRequest request) {
final UserInfoDetails userDetails = userDetails(loginParam);
SecurityFrameworkUtils.setAuthor(userDetails, request);
final String sessionId = IdUtil.fastSimpleUUID();
final UserDetailsDO loginUser= new UserDetailsDO(sessionId, userDetails.getUsername(), ToolsUtil.getClientIp(), ToolsUtil.getUserAgent(), tokenLiveSeconds, userDetails);
userDetailsRepository.save(loginUser);
return new LoginUserDTO(userDetails.getUsername(), sessionId, loginParam.getRedirectUrl(), userDetails.getPhoto(), userDetails.getDepartmentDetails().getName());
}
/**
* 登录认证
*
* @param login 登录参数
* @return 用户信息
*/
private UserInfoDetails userDetails(LoginParam login) {
Authentication authenticate;
try {
authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(login.getUsername(), login.getPassword()));
} catch (BadCredentialsException badCredentialsException) {
throw new BusinessException("密码错误");
} catch (DisabledException disabledException) {
throw new BusinessException("账号禁用");
} catch (LockedException lockedException) {
throw new BusinessException("账号不存在部门信息");
} catch (AuthenticationException authenticationException) {
throw new BusinessException("登录失败");
}
Assert.notNull(authenticate.getPrincipal(), "Principal 不会为空");
return (UserInfoDetails) authenticate.getPrincipal();
}
}
3、AccessDeniedHandlerImpl
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
log.info("用户[{}]访问URL[{}]权限不足", SecurityFrameworkUtils.get(), request.getRequestURI(), e);
ServletUtils.writeJSON(response, ResultDTO.getInstance(ResultStateEnum.NO_AUTHORITY));
}
}
4、AuthenticationEntryPointImpl
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {
log.debug("请求URI[{}]未登录", request.getRequestURI());
ServletUtils.writeJSON(response, ResultDTO.getInstance(ResultStateEnum.NO_LOGIN));
}
}
5、TokenVerificationFilter
public class TokenVerificationFilter extends OncePerRequestFilter {
private final UserDetailsRepository userDetailsRepository;
public TokenVerificationFilter(UserDetailsRepository userDetailsRepository) {
this.userDetailsRepository = userDetailsRepository;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
final String token = request.getHeader(HttpHeaders.AUTHORIZATION);
if (StringUtils.hasText(token)) {
final Optional optional = userDetailsRepository.findById(token);
optional.ifPresent(loginUserRd -> SecurityFrameworkUtils.setAuthor(loginUserRd.getUserDetails(), request));
}
filterChain.doFilter(request, response);
}
}
6、LogoutSuccessHandlerImpl
@Slf4j
@Component
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
private final UserDetailsRepository userDetailsRepository;
public LogoutSuccessHandlerImpl(UserDetailsRepository userDetailsRepository) {
this.userDetailsRepository = userDetailsRepository;
}
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
final String token = request.getHeader(HttpHeaders.AUTHORIZATION);
log.info("用户token[{}]发起退出", token);
if (StringUtils.hasText(token)) {
userDetailsRepository.deleteById(token);
}
ServletUtils.writeJSON(response, ResultDTO.success());
}
}
7、UserDetailsRepository
public interface UserDetailsRepository extends CrudRepository {
}
8、UserDetailsDO
@RedisHash(value = "user")
@Data
public class UserDetailsDO {
@Id
private String key;
@Indexed
private String username;
private String clientIp;
private String userAgent;
@TimeToLive
private Long liveSeconds;
private UserInfoDetails userDetails;
}
使用 Redis Repositories解决用户重复登录问题 注意添加注解 @EnableRedisRepositories