加密方式
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
获得一个加密后的密码存进数据库,用于第一次登录
设置一些角色
分配给这个用户系统管理员的角色
接着需要一个springSecurity能识别的用户类,即一个实体实现UserDetails接口
@Getter
@Setter
@ApiModel(value = "User对象", description = "")
@Builder
public class User implements UserDetails {
@TableId(value = "id", type = IdType.AUTO)
private Integer id;
@ApiModelProperty("账号")
private String account;
private String password;
@ApiModelProperty("姓名")
private String name;
@ApiModelProperty("是否禁用")
private Boolean disable;
@ApiModelProperty("邮箱")
private String mail;
private Set<String> roles;//用户一登陆会初始化这些参数
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toSet());
}
@Override
public String getUsername() {
return this.account;
}
@Override
public boolean isAccountNonExpired() {//账号是否没过期
return true;
}
@Override
public boolean isAccountNonLocked() {//账号是否没有被锁定
return true;
}
@Override
public boolean isCredentialsNonExpired() {//凭证是否没有过期
return true;
}
@Override
public boolean isEnabled() {//账号是否能用
return !this.disable;
}
}
然后需要一个Service类实现UserDetailsService接口,来重写方法,使SpringSecurity获得登陆人账号密码并校验的能力
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService, UserDetailsService {
@Autowired
UserMapper userMapper;
@Autowired
UserRoleMapper userRoleMapper;
@Autowired
RoleMapper roleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
if (StringUtils.isBlank(username)) {//null,""," "都会返回true
throw new BizException("用户名不能为空!");
}
User user = userMapper.selectOne(new LambdaQueryWrapper<User>()
.select(User::getId, User::getAccount, User::getName, User::getPassword, User::getDisable, User::getMail)
.eq(User::getAccount, username));
if(Objects.isNull(user)){
throw new BizException("用户名不存在");
}
return convertUserDetail(user);
}
private UserDetails convertUserDetail(User user) {//封装登录信息
List<Object> objectList = userRoleMapper.selectObjs(new LambdaQueryWrapper<UserRole>().select(UserRole::getRoleId).eq(UserRole::getUserId,user.getId()));
List<Object> roleNamesObject = roleMapper.selectObjs(new LambdaQueryWrapper<Role>().select(Role::getName).in(Role::getId, objectList));
Set<String> roleNames = roleNamesObject.stream().map(Object::toString).collect(Collectors.toSet());
user.setRoles(roleNames);
return user;
}
}
当前端调用登录接口时,就会调用loadUserByUsername方法,返回的UserDetails封装了用户的个人信息。
配置类
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private AuthenticationEntryPointImpl authenticationEntryPoint;//未登录处理
@Autowired
private AccessDeniedHandlerImpl accessDeniedHandler;//权限不足处理
@Autowired
private AuthenticationSuccessHandlerImpl authenticationSuccessHandler;//登录成功处理
@Autowired
private AuthenticationFailHandlerImpl authenticationFailHandler;//登录失败处理
@Autowired
private LogoutSuccessHandlerImpl logoutSuccessHandler;//登出成功处理
@Bean
public FilterInvocationSecurityMetadataSource securityMetadataSource() {
return new FilterInvocationSecurityMetadataSourceImpl();//接口拦截规则
}
@Bean
public AccessDecisionManager accessDecisionManager() {
return new AccessDecisionManagerImpl();//访问决策管理器
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public HttpSessionEventPublisher httpSessionEventPublisher() {
return new HttpSessionEventPublisher();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 配置登录注销路径
http.formLogin()
.loginProcessingUrl("/login")
.successHandler(authenticationSuccessHandler)
.failureHandler(authenticationFailHandler)
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(logoutSuccessHandler);
// 配置路由权限信息
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O fsi) {
fsi.setSecurityMetadataSource(securityMetadataSource());//接口拦截规则
fsi.setAccessDecisionManager(accessDecisionManager());//访问决策管理器
return fsi;
}
})
.anyRequest()
.permitAll()
.and()
// 关闭跨站请求防护
.csrf().disable().exceptionHandling()
// 未登录处理
.authenticationEntryPoint(authenticationEntryPoint)
// 权限不足处理
.accessDeniedHandler(accessDeniedHandler)
.and()
.sessionManagement()
.maximumSessions(20)
.sessionRegistry(sessionRegistry());
}
}
这里可以设置7个自定义的处理器:
/**
* 接口拦截规则
*
* @author chengzige
* @date 2022/11/19
*/
@Component
public class FilterInvocationSecurityMetadataSourceImpl implements FilterInvocationSecurityMetadataSource {
/**
* 资源角色列表
*/
private static List<Operation> resourceRoleList;//静态
@Autowired
private OperationMapper operationMapper;
/**
* 加载资源角色信息
*/
@PostConstruct
private void loadDataSource() {
resourceRoleList = operationMapper.listResourceRoles();//listResourceRoles已经排除了允许匿名的接口
}
/**
* 清空接口角色信息
*/
public void clearDataSource() {
resourceRoleList = null;
}
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
// 修改接口角色关系后重新加载
if (CollectionUtils.isEmpty(resourceRoleList)) {
this.loadDataSource();
}
FilterInvocation fi = (FilterInvocation) object;
// 获取用户请求方式
String method = fi.getRequest().getMethod();
// 获取用户请求Url
String url = fi.getRequest().getRequestURI();
AntPathMatcher antPathMatcher = new AntPathMatcher();
// 获取接口角色信息,若为匿名接口则放行,若无对应角色则禁止
for (Operation operation : resourceRoleList) {
if (antPathMatcher.match(operation.getUrl(), url) && operation.getRequestMethod().equals(method)) {
List<String> roleList = operation.getRoles();
if (CollectionUtils.isEmpty(roleList)) {
return SecurityConfig.createList("disable");
}
return SecurityConfig.createList(roleList.toArray(new String[]{}));
}
}
return null;//return null就不会经过访问决策管理器了,即跳过AccessDecisionManagerImpl,放行
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return FilterInvocation.class.isAssignableFrom(aClass);
}
}
/**
* 访问决策管理器
*
* @author chengzige
* @date 2022/11/19
*/
@Component
public class AccessDecisionManagerImpl implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
// 获取用户拥有的角色列表
List<String> permissionList = authentication.getAuthorities()
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toList());
for (ConfigAttribute item : collection) {//collection存储了访问应该具备的角色列表
if (permissionList.contains(item.getAttribute())) {
return;//有权限就直接return,
}
}
throw new AccessDeniedException("没有操作权限");//没有权限就抛出异常
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
/**
* 用户权限不足处理
*
* @author chengzige
* @date 2022/11/19
*/
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Autowired
ObjectMapper objectMapper;
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException {
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(Result.fail("权限不足")));
}
}
/**
* 用户未登录处理
*
* @author chengzige
* @date 2022/11/19
*/
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Autowired
ObjectMapper objectMapper;
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(Result.fail("用户未登录")));
}
}
/**
* 登录失败处理
*
* @author chengzige
* @date 2022/11/19
*/
@Component
public class AuthenticationFailHandlerImpl implements AuthenticationFailureHandler {
@Autowired
ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException {
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(Result.fail(e.getMessage())));
}
}
/**
* 登录成功处理
*
* @author chengzige
* @date 2022/11/19
*/
@Component
public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
@Autowired
ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
// 返回登录信息
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(UserUtils.getLoginUser()));
}
}
/**
* 登出成功处理
*
* @author chengzige
* @date 2022/11/19
*/
@Component
public class LogoutSuccessHandlerImpl implements LogoutSuccessHandler {
@Autowired
ObjectMapper objectMapper;
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(Result.ok()));
}
}
配置之后swagger不会检查到login和logout接口,再定义一个处理器处理swagger文档
@Component
public class SwaggerHandler implements ApiListingScannerPlugin {
@Override
public List<ApiDescription> apply(DocumentationContext documentationContext) {
//登录接口
//1.定义参数
Parameter username = new ParameterBuilder()
.name("username")
.description("用户名")
.type(new TypeResolver().resolve(String.class))
.modelRef(new ModelRef("string"))
.parameterType("form")
.required(true)
.defaultValue("admin")
.build();
Parameter password = new ParameterBuilder()
.name("password")
.description("密码")
.type(new TypeResolver().resolve(String.class))
.modelRef(new ModelRef("string"))
.parameterType("form")
.required(true)
.defaultValue("123456")
.build();
//2.接口的每种请求方式(GET/POST...)为一个 Operation
Operation loginOperation = new OperationBuilder(new CachingOperationNameGenerator())
.method(HttpMethod.POST)
.summary("登录")
.tags(Sets.newHashSet("用户模块"))
.responseMessages(Sets.newHashSet(new ResponseMessageBuilder().code(200).message("OK").build()))
.consumes(Sets.newHashSet(MediaType.MULTIPART_FORM_DATA_VALUE))
.produces(Sets.newHashSet(MediaType.APPLICATION_JSON_VALUE))
.parameters(Arrays.asList(username, password))
.build();
//3.每个接口路径对应一个 ApiDescription
ApiDescription loginDesc = new ApiDescription(null, "/login", "登录", Collections.singletonList(loginOperation), false);
//登出接口
Operation logoutOperation = new OperationBuilder(new CachingOperationNameGenerator())
.method(HttpMethod.GET)
.summary("登出")
.notes("退出登录")
.tags(Sets.newHashSet("用户模块"))
.responseMessages(Sets.newHashSet(new ResponseMessageBuilder().code(200).message("OK").build()))
.build();
ApiDescription logoutDesc = new ApiDescription(null, "/logout", "注销", Collections.singletonList(logoutOperation), false);
documentationContext.getTags().add(new Tag("用户模块", "User Controller"));
return new ArrayList<>(Arrays.asList(loginDesc, logoutDesc));
}
@Override
public boolean supports(@NotNull DocumentationType documentationType) {
return DocumentationType.SWAGGER_2.equals(documentationType);
}
}