加密方式
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

获得一个加密后的密码存进数据库,用于第一次登录

spring cloud gateway拦截并返回数据_java

设置一些角色

spring cloud gateway拦截并返回数据_User_02

分配给这个用户系统管理员的角色

spring cloud gateway拦截并返回数据_java_03

接着需要一个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);
    }

}