Sping Security前后端分离两种方案

news2024/11/16 9:56:19

前言

本篇文章是基于Spring Security实现前后端分离登录认证及权限控制的实战,主要包括以下四方面内容:

  1. Spring Seciruty简单介绍;

  2. 通过Spring Seciruty实现的基于表单和Token认证的两种认证方式;

  3. 自定义实现RBAC的权限控制;

  4. 跨域问题处理;

Spring Seciruty简单介绍

Spring Security是基于Spring框架,提供了一套Web应用安全性的完整解决方案。关于安全方面的两个核心功能是认证和授权,Spring Security重要核心功能就是实现用户认证(Authentication)和用户授权(Authorization)。

认证(Authentication)

认证是用来验证某个用户能否访问该系统。用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证过程。认证获取:点击获取

授权(Authorization)

授权发生在认证之后,用来验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。

实现简单介绍

Spring Security进行认证和鉴权的时候,采用的一系列的Filter来进行拦截的。 下图是Spring Security基于表单认证授权的流程,

在Spring Security一个请求想要访问到API就会从左到右经过蓝线框里的过滤器,其中绿色部分是负责认证的过滤器,蓝色部分是负责异常处理,橙色部分则是负责授权。进过一系列拦截最终访问到我们的API。

准备阶段

整个项目结构如下,demo1部分是基于表单的认证,demo2部分是基于Token的认证,数据库采用是Mysql,访问数据库使用的JPA,Spring Boot版本是2.7.8,Spring Security版本是比较新的5.7.6,这里需要注意的是Spring Security5.7以后版本和前面的版本有一些差异,未来该Demo的版本的问题一直会持续保持升级。 后续也会引用前端项目,前端后台管理部分我个人感觉后端程序员也要进行简单的掌握一些,便于工作中遇到形形色色问题更好的去处理。

Maven

关于Maven部分细节这里不进行展示了,采用的父子工程,主要简单看下依赖的版本,

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <springboot.vetsion>2.7.8</springboot.vetsion>
        <mysql-connector-java.version>5.1.46</mysql-connector-java.version>
        <org.projectlombok.version>1.16.14</org.projectlombok.version>
        <jjwt.version>0.11.1</jjwt.version>
        <fastjson.version>1.2.75</fastjson.version>
    </properties>

统一错误码

public enum ResultCode {

    /* 成功 */
    SUCCESS(200, "成功"),

    /* 默认失败 */
    COMMON_FAIL(999, "失败"),

    /* 参数错误:1000~1999 */
    PARAM_NOT_VALID(1001, "参数无效"),
    PARAM_IS_BLANK(1002, "参数为空"),
    PARAM_TYPE_ERROR(1003, "参数类型错误"),
    PARAM_NOT_COMPLETE(1004, "参数缺失"),

    /* 用户错误 */
    USER_NOT_LOGIN(2001, "用户未登录"),
    USER_ACCOUNT_EXPIRED(2002, "账号已过期"),
    USER_CREDENTIALS_ERROR(2003, "密码错误"),
    USER_CREDENTIALS_EXPIRED(2004, "密码过期"),
    USER_ACCOUNT_DISABLE(2005, "账号不可用"),
    USER_ACCOUNT_LOCKED(2006, "账号被锁定"),
    USER_ACCOUNT_NOT_EXIST(2007, "账号不存在"),
    USER_ACCOUNT_ALREADY_EXIST(2008, "账号已存在"),
    USER_ACCOUNT_USE_BY_OTHERS(2009, "账号下线"),

    /* 业务错误 */
    NO_PERMISSION(3001, "没有权限");
    private Integer code;
    private String message;

    ResultCode(Integer code, String message) {
        this.code = code;
        this.message = message;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    private static Map<Integer, ResultCode> map = new HashMap<>();
    private static Map<String, ResultCode> descMap = new HashMap<>();


    static {
        for (ResultCode value : ResultCode.values()) {
            map.put(value.getCode(), value);
            descMap.put(value.getMessage(), value);
        }
    }

    public static ResultCode getByCode(Integer code) {
        return map.get(code);
    }

    public static ResultCode getByMessage(String desc) {
        return descMap.get(desc);
    }
}

统一返回定义

public class CommonResponse<T> implements Serializable {

    /**
     * 成功状态码
     */
    private final static String SUCCESS_CODE = "SUCCESS";

    /**
     * 提示信息
     */
    private String message;

    /**
     * 返回数据
     */
    private T data;

    /**
     * 状态码
     */
    private Integer code;

    /**
     * 状态
     */
    private Boolean state;

    /**
     * 错误明细
     */
    private String detailMessage;


    /**
     * 成功
     *
     * @param <T> 泛型
     * @return 返回结果
     */
    public static <T> CommonResponse<T> ok() {
        return ok(null);
    }

    /**
     * 成功
     *
     * @param data 传入的对象
     * @param <T>  泛型
     * @return 返回结果
     */
    public static <T> CommonResponse<T> ok(T data) {
        CommonResponse<T> response = new CommonResponse<T>();
        response.code = ResultCode.SUCCESS.getCode();
        response.data = data;
        response.message = "返回成功";
        response.state = true;
        return response;
    }

    /**
     * 错误
     *
     * @param code    自定义code
     * @param message 自定义返回信息
     * @param <T>     泛型
     * @return 返回信息
     */
    public static <T> CommonResponse<T> error(Integer code, String message) {
        return error(code, message, null);
    }

    /**
     * 错误
     *
     * @param code          自定义code
     * @param message       自定义返回信息
     * @param detailMessage 错误详情信息
     * @param <T>           泛型
     * @return 返回错误信息
     */
    public static <T> CommonResponse<T> error(Integer code, String message,
                                              String detailMessage) {
        CommonResponse<T> response = new CommonResponse<T>();
        response.code = code;
        response.data = null;
        response.message = message;
        response.state = false;
        response.detailMessage = detailMessage;
        return response;
    }

    public Boolean getState() {
        return state;
    }

    public CommonResponse<T> setState(Boolean state) {
        this.state = state;
        return this;
    }

    public String getMessage() {
        return message;
    }

    public CommonResponse<T> setMessage(String message) {
        this.message = message;
        return this;
    }

    public T getData() {
        return data;
    }

    public CommonResponse<T> setData(T data) {
        this.data = data;
        return this;
    }

    public Integer getCode() {
        return code;
    }

    public CommonResponse<T> setCode(Integer code) {
        this.code = code;
        return this;
    }

    public String getDetailMessage() {
        return detailMessage;
    }

    public CommonResponse<T> setDetailMessage(String detailMessage) {
        this.detailMessage = detailMessage;
        return this;
    }
}

数据库设计

基于RBAC模型最简单奔版本的数据库设计,用户、角色、权限表;

基于表单认证

对于表单认证整体过程可以参考下图,

核心配置

核心配置包含了框架开启以及权限配置,这部分内容是重点要关注的,这里可以看到所有重写的内容,主要包含以下七方面内容:

  1. 定义哪些资源不需要认证,哪些需要认证,这里我采用注解形式;

  2. 实现自定义认证以及授权异常的接口;

  3. 实现自定义登录成功以及失败的接口;

  4. 实现自定义登出以后的接口;

  5. 实现自定义重数据查询对应的账号权限的接口;

  6. 自定义加密的Bean;

  7. 自定义授权认证Bean;

当然Spring Security还支持更多内容,比如限制用户登录个数等等,这里部分内容使用不是太多,后续大家如果有需要我也可以进行补充。

//Spring Security框架开启
@EnableWebSecurity
//授权全局配置
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfig {

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private NotAuthenticationConfig notAuthenticationConfig;


    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        //支持跨域
        http.cors().and()
                //csrf关闭
                .csrf().disable()
                //配置哪些需要认证 哪些不需要认证
                .authorizeRequests(rep -> rep.antMatchers(notAuthenticationConfig.getPermitAllUrls().toArray(new String[0]))
                        .permitAll().anyRequest().authenticated())
                .exceptionHandling()
                //认证异常处理
                .authenticationEntryPoint(new ResourceAuthExceptionEntryPoint())
                //授权异常处理
                .accessDeniedHandler(new CustomizeAccessDeniedHandler())
                //登录认证处理
                .and().formLogin()
                .successHandler(new CustomizeAuthenticationSuccessHandler())
                .failureHandler(new CustomizeAuthenticationFailureHandler())
                //登出
                .and().logout().permitAll().addLogoutHandler(new CustomizeLogoutHandler())
                .logoutSuccessHandler(new CustomizeLogoutSuccessHandler())
                .deleteCookies("JSESSIONID")
                //自定义认证
                .and().userDetailsService(sysUserService);
        return http.build();
    }


    @Bean
    public PasswordEncoder passwordEncoder() {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        return bCryptPasswordEncoder;
    }

    @Bean("ssc")
    public SecuritySecurityCheckService permissionService() {
        return new SecuritySecurityCheckService();
    }
    
}

通过注解形式实现哪些需要资源不需要认证

通过自定义注解@NotAuthentication,然后通过实现InitializingBean接口,实现加载不需要认证的资源,支持类和方法,使用就是通过在方法或者类打上对应的注解。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface NotAuthentication {
}

@Service
public class NotAuthenticationConfig implements InitializingBean, ApplicationContextAware {

    private static final String PATTERN = "\\{(.*?)}";

    public static final String ASTERISK = "*";


    private ApplicationContext applicationContext;

    @Getter
    @Setter
    private List<String> permitAllUrls = new ArrayList<>();

    @Override
    public void afterPropertiesSet() throws Exception {
        RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
        Map<RequestMappingInfo, HandlerMethod> map = mapping.getHandlerMethods();
        map.keySet().forEach(x -> {
            HandlerMethod handlerMethod = map.get(x);

            // 获取方法上边的注解 替代path variable 为 *
            NotAuthentication method = AnnotationUtils.findAnnotation(handlerMethod.getMethod(), NotAuthentication.class);
            Optional.ofNullable(method).ifPresent(inner -> Objects.requireNonNull(x.getPathPatternsCondition())
                    .getPatternValues().forEach(url -> permitAllUrls.add(url.replaceAll(PATTERN, ASTERISK))));

            // 获取类上边的注解, 替代path variable 为 *
            NotAuthentication controller = AnnotationUtils.findAnnotation(handlerMethod.getBeanType(), NotAuthentication.class);
            Optional.ofNullable(controller).ifPresent(inner -> Objects.requireNonNull(x.getPathPatternsCondition())
                    .getPatternValues().forEach(url -> permitAllUrls.add(url.replaceAll(PATTERN, ASTERISK))));
        });
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

自定义认证异常实现

AuthenticationEntryPoint�用来解决匿名用户访问无权限资源时的异常。

public class ResourceAuthExceptionEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
        CommonResponse result= CommonResponse.error(ResultCode.USER_NOT_LOGIN.getCode(),
                ResultCode.USER_NOT_LOGIN.getMessage());
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        response.getWriter().write(JSON.toJSONString(result));
    }
}

自定义授权异常实现

AccessDeniedHandler用来解决认证过的用户访问无权限资源时的异常。

public class CustomizeAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        CommonResponse result = CommonResponse.error(ResultCode.NO_PERMISSION.getCode(),
                        ResultCode.NO_PERMISSION.getMessage());
        //处理编码方式,防止中文乱码的情况
        response.setContentType("text/json;charset=utf-8");
        //塞到HttpServletResponse中返回给前台
        response.getWriter().write(JSON.toJSONString(result));
    }
}

自定义登录成功、失败

AuthenticationSuccessHandler和AuthenticationFailureHandler这两个接口用于登录成功失败以后的处理。

public class CustomizeAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        AuthUser authUser = (AuthUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        //返回json数据
        CommonResponse<AuthUser> result = CommonResponse.ok(authUser);
        //处理编码方式,防止中文乱码的情况
        response.setContentType("text/json;charset=utf-8");
        //塞到HttpServletResponse中返回给前台
        response.getWriter().write(JSON.toJSONString(result));
    }
}

public class CustomizeAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
        //返回json数据
        CommonResponse result = null;
        if (exception instanceof AccountExpiredException) {
            //账号过期
            result = CommonResponse.error(ResultCode.USER_ACCOUNT_EXPIRED.getCode(), ResultCode.USER_ACCOUNT_EXPIRED.getMessage());
        } else if (exception instanceof BadCredentialsException) {
            //密码错误
            result = CommonResponse.error(ResultCode.USER_CREDENTIALS_ERROR.getCode(), ResultCode.USER_CREDENTIALS_ERROR.getMessage());
//        } else if (exception instanceof CredentialsExpiredException) {
//            //密码过期
//            result = CommonResponse.error(ResultCode.USER_CREDENTIALS_EXPIRED);
//        } else if (exception instanceof DisabledException) {
//            //账号不可用
//            result = CommonResponse.error(ResultCode.USER_ACCOUNT_DISABLE);
//        } else if (exception instanceof LockedException) {
//            //账号锁定
//            result = CommonResponse.error(ResultCode.USER_ACCOUNT_LOCKED);
//        } else if (exception instanceof InternalAuthenticationServiceException) {
//            //用户不存在
//            result = CommonResponse.error(ResultCode.USER_ACCOUNT_NOT_EXIST);
        } else {
            //其他错误
            result = CommonResponse.error(ResultCode.COMMON_FAIL.getCode(), ResultCode.COMMON_FAIL.getMessage());
        }
        //处理编码方式,防止中文乱码的情况
        response.setContentType("text/json;charset=utf-8");
        //塞到HttpServletResponse中返回给前台
        response.getWriter().write(JSON.toJSONString(result));
    }
}

自定义登出

LogoutHandler自定义登出以后处理逻辑,比如记录在线时长等等;LogoutSuccessHandler登出成功以后逻辑处理。

public class CustomizeLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {

        CommonResponse result = CommonResponse.ok();
        response.setContentType("text/json;charset=utf-8");
        response.getWriter().write(JSON.toJSONString(result));
    }
}

public class CustomizeLogoutHandler implements LogoutHandler {

    @Override
    public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {

    }
}

自定义认证

自定义认证涉及三个对象UserDetialsService、UserDetails以及PasswordEncoder,整个流程首先根据用户名查询出用户对象交由UserDetialsService接口处理,该接口只有一个方法loadUserByUsername,通过用户名查询用户对象。查询出来的用户对象需要通过Spring Security中的用户数据UserDetails实体类来体现,这里使用AuthUser继承User,User本质上就是继承与UserDetails,UserDetails该类中提供了账号、密码等通用属性。对密码进行校验使用PasswordEncoder组件,负责密码加密与校验。

public class AuthUser extends User {

    public AuthUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
    }
}

@Service
public class SysUserService implements UserDetailsService {

    @Autowired
    private SysUserRepository sysUserRepository;

    @Autowired
    private SysRoleService sysRoleService;

    @Autowired
    private SysMenuService sysMenuService;


    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<SysUser> sysUser = Optional.ofNullable(sysUserRepository.findOptionalByUsername(username).orElseThrow(() ->
                new UsernameNotFoundException("未找到用户名")));
        List<SysRole> roles = sysRoleService.queryByUserName(sysUser.get().getUsername());
        Set<String> dbAuthsSet = new HashSet<>();
        if (!CollectionUtils.isEmpty(roles)) {
            //角色
            roles.forEach(x -> {
                dbAuthsSet.add("ROLE_" + x.getName());
            });
            List<Long> roleIds = roles.stream().map(SysRole::getId).collect(Collectors.toList());
            List<SysMenu> menus = sysMenuService.queryByRoleIds(roleIds);
            //菜单
            Set<String> permissions= menus.stream().filter(x->x.getType().equals(3))
                    .map(SysMenu::getPermission).collect(Collectors.toSet());
            dbAuthsSet.addAll(permissions);
        }
        Collection<GrantedAuthority> authorities = AuthorityUtils
                .createAuthorityList(dbAuthsSet.toArray(new String[0]));
        return new AuthUser(username, sysUser.get().getPassword(), authorities);
    }
}

基于Token认证

基于Token认证这里我采用JWT方式,下图是整个处理的流程,通过自定义的登录以及JwtAuthenticationTokenFilter来完成整个任务的实现,需要注意的是这里我没有使用缓存。

核心配置

与表单认证不同的是这里关闭和FormLogin表单认证以及不使用Session方式,增加了JwtAuthenticationTokenFilter,此外ResourceAuthExceptionEntryPoint兼职处理之前登录失败以后的异常处理。

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfig {

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private NotAuthenticationConfig notAuthenticationConfig;



    @Bean
    SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        //支持跨域
        http.cors().and()
                //csrf关闭
                .csrf().disable()
                //不使用session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().authorizeRequests(rep -> rep.antMatchers(notAuthenticationConfig.getPermitAllUrls().toArray(new String[0]))
                        .permitAll().anyRequest().authenticated())
                .exceptionHandling()
                //异常认证
                .authenticationEntryPoint(new ResourceAuthExceptionEntryPoint())
                .accessDeniedHandler(new CustomizeAccessDeniedHandler())
                .and()
                //token过滤
                .addFilterBefore(new JwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class)
                .userDetailsService(sysUserService);
        return http.build();
    }


    /**
     * 获取AuthenticationManager
     *
     * @param configuration
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    /**
     * 密码
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        return bCryptPasswordEncoder;
    }

    @Bean("ssc")
    public SecuritySecurityCheckService permissionService() {
        return new SecuritySecurityCheckService();
    }

}

Token创建

@Service
public class LoginService {


    @Autowired
    private AuthenticationManager authenticationManager ;

    @Autowired
    private SysUserService sysUserService;


    public LoginVO login(LoginDTO loginDTO) {
        //创建Authentication对象
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginDTO.getUsername(),
                loginDTO.getPassword());

        //调用AuthenticationManager的authenticate方法进行认证
        Authentication authentication = authenticationManager.authenticate(authenticationToken);

        if(authentication == null) {
            throw new RuntimeException("用户名或密码错误");
        }
        //登录成功以后用户信息、
        AuthUser authUser =(AuthUser)authentication.getPrincipal();

        LoginVO loginVO=new LoginVO();
        loginVO.setUserName(authUser.getUsername());
        loginVO.setAccessToken(JwtUtils.createAccessToken(authUser));
        loginVO.setRefreshToken(JwtUtils.createRefreshToken(authUser));

        return loginVO;
    }


    public LoginVO refreshToken(String accessToken, String refreshToken){
        if (!JwtUtils.validateRefreshToken(refreshToken) && !JwtUtils.validateWithoutExpiration(accessToken)) {
            throw new RuntimeException("认证失败");
        }
        Optional<String> userName = JwtUtils.parseRefreshTokenClaims(refreshToken).map(Claims::getSubject);
        if (userName.isPresent()){
            AuthUser authUser = sysUserService.loadUserByUsername(userName.get());
            if (Objects.nonNull(authUser)) {
                LoginVO loginVO=new LoginVO();
                loginVO.setUserName(authUser.getUsername());
                loginVO.setAccessToken(JwtUtils.createAccessToken(authUser));
                loginVO.setRefreshToken(JwtUtils.createRefreshToken(authUser));
                return loginVO;
            }
            throw new InternalAuthenticationServiceException("用户不存在");
        }
        throw new RuntimeException("认证失败");
    }
}

Token过滤

public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        //check Token
        if (checkJWTToken(request)) {
            //解析token中的认证信息
            Optional<Claims> claimsOptional = validateToken(request)
                    .filter(claims -> claims.get("authorities") != null);
            if (claimsOptional.isPresent()) {
                List<String> authoritiesList = castList(claimsOptional.get().get("authorities"), String.class);
                List<SimpleGrantedAuthority> authorities = authoritiesList
                        .stream().map(String::valueOf)
                        .map(SimpleGrantedAuthority::new).collect(Collectors.toList());
                UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
                        new UsernamePasswordAuthenticationToken(claimsOptional.get().getSubject(), null, authorities);
                SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            } else {
                SecurityContextHolder.clearContext();
            }
        }
        chain.doFilter(request, response);
    }

    public static <T> List<T> castList(Object obj, Class<T> clazz) {
        List<T> result = new ArrayList<T>();
        if (obj instanceof List<?>) {
            for (Object o : (List<?>) obj) {
                result.add(clazz.cast(o));
            }
            return result;
        }
        return null;
    }

    private Optional<Claims> validateToken(HttpServletRequest req) {
        String jwtToken = req.getHeader("token");
        try {
            return JwtUtils.parseAccessTokenClaims(jwtToken);
        } catch (ExpiredJwtException | SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException e) {
            //输出日志
            return Optional.empty();
        }
    }

    private boolean checkJWTToken(HttpServletRequest request) {
        String authenticationHeader = request.getHeader("token");
        return authenticationHeader != null;
    }
}

授权处理

全局授权的配置已经在核心配置中开启,核心思路是通过SecurityContextHolder获取当前用户权限,判断当前用户的权限是否包含该方法的权限,此部分设计后续如果存在性能问题,可以设计缓存来解决。

授权检查

public class SecuritySecurityCheckService {


    public boolean hasPermission(String permission) {
        return hasAnyPermissions(permission);
    }

    public boolean hasAnyPermissions(String... permissions) {
        if (CollectionUtils.isEmpty(Arrays.asList(permissions))) {
            return false;
        }
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            return false;
        }
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        return authorities.stream().map(GrantedAuthority::getAuthority).filter(x -> !x.contains("ROLE_"))
                .anyMatch(x -> PatternMatchUtils.simpleMatch(permissions, x));
    }

    public boolean hasRole(String role) {
        return hasAnyRoles(role);
    }

    public boolean hasAnyRoles(String... roles) {
        if (CollectionUtils.isEmpty(Arrays.asList(roles))) {
            return false;
        }
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null) {
            return false;
        }
        Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
        return authorities.stream().map(GrantedAuthority::getAuthority).filter(x -> x.contains("ROLE_"))
                .anyMatch(x -> PatternMatchUtils.simpleMatch(roles, x));
    }
}

如何使用

@PreAuthorize("@ssc.hasPermission('sys:user:query')")
@PostMapping("/helloWord")
public String hellWord(){
  return "hello word";
}

跨域问题处理

关于这部分跨域部分的配置还可以更加细化一点。

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 设置允许跨域的路径
        registry.addMapping("/**")
                // 设置允许跨域请求的域名
                .allowedOriginPatterns("*")
                // 是否允许cookie
                .allowCredentials(true)
                // 设置允许的请求方式
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                // 设置允许的header属性
                .allowedHeaders("*")
                // 跨域允许时间
                .maxAge(3600);
    }
}

vue-admin-template登录的简单探索感悟

这部分就是有些感悟(背景自身是没有接触过Vue相关的知识),具体的感悟就是不要畏惧一些自己不知道以及不会的东西,大胆的去尝试,因为自身的潜力是很大的。为什么要这么讲,通过自己折腾3个小时,自己完成整个登录过程,如果是前端可能会比较简单,针对我这种从没接触过的还是有些难度的,需要一些基础配置更改以及流程梳理,这里简单来让大家看下效果,后续我也会自己把剩下菜单动态加载以及一些简单表单交互来完成,做到简单的表单可以自己来实现。

文章转载自:大魔王先生

原文链接:https://www.cnblogs.com/wtzbk/p/17260243.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/739242.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Django_模板继承

模板继承先创建一个父模版&#xff0c;它包含大部页面共有元素&#xff0c;并且需要定义能够被子模板覆盖的blocks标签。 extends 模板继承 通过下面的例子&#xff0c;理解模板继承的概念。 创建base.html文件&#xff0c;写入下面代码&#xff1a; <!DOCTYPE html>…

Stable Diffusion - 墨幽人造人 模型与 Tag 配置

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/131565068 墨幽人造人模型的版本介绍&#xff1a; v1010修剪&#xff1a;更小体积的4G修剪版&#xff0c;但是整体色彩及细节表现力、好手好脸概…

结构体和数据结构--动态数据结构体-单向链表

目录 一、问题的提出 二、链表的定义 三、单向链表的建立 四、单向链表的删除操作 五、单向链表的插入操作 一、问题的提出 数组实质是一种顺序存储&#xff0c;随机访问的线性表&#xff0c;它的优点是使用直观&#xff0c;便于快速、随机地存取线性表中地任意元素。但缺…

线性表综合应用题1

线性表综合应用题1 从顺序表中删除具有最小值的元素&#xff08;假设唯一&#xff09;并由函数返回被删除元素的值。空出的位置由最后一个元素填补&#xff0c;若顺序表为空&#xff0c;则显示出错信息并退出运行。 算法思想&#xff1a;搜索整个顺序表&#xff0c;查找最小值元…

多元回归预测 | Matlab基于鲸鱼算法(WOA)优化高斯过程回归(WOA-GPR)的数据回归预测,matlab代码,多变量输入模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab基于鲸鱼算法(WOA)优化高斯过程回归(WOA-GPR)的数据回归预测,matlab代码,多变量输入模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源…

ct.js笔记-整合SweetAlert2,弹出输入框(添加Cat模组)

如下&#xff1a; 效果如下&#xff1a; 点击Name后&#xff1a; 实现如下&#xff1a; 在ct.libs中添加个新文件夹sweetalert2文件夹。 内部要包含这几个关键文件&#xff1a; ①index.js&#xff1a;调用模组的入库文件&#xff1b; ②includes文件夹&#xff1a;把需要引…

keepalived 实现 IP 地址漂移

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…

基于redis实现延时队列(二)

背景 上篇文章中使用了redis的zset定时器实现延时任务&#xff0c;虽然定时器设置为30秒执行一次&#xff0c;但是还是有时间上的差异化&#xff1b;现更换一种方式实现&#xff0c;可以避免时间上的差异。 redis的key过期回调事件&#xff0c;也能达到延迟队列效果 配置修改…

抖音短视频seo矩阵源码开源SaaS部署(四)

目录 一、 抖音短视频SEO矩阵包括以下内容&#xff1a; ​编辑 二、 源码开源部署流程 三、 SaaS的理解 四、 抖音短视频seo&#xff0c;SaaS矩阵部署用到的技术 抖音短视频SEO主要是通过以下几个方面实现的&#xff1a; SaaS矩阵部署主要用到的技术包括以下几个方面&…

深蓝学院C++基础与深度解析笔记 第 11 章 类

深蓝学院C基础与深度解析笔记 第 11 章 类 1. 结构体与对象聚合 **● 结构体&#xff1a;**对基本数据结构进行扩展&#xff0c;将多个对象放置在一起视为一个整体 – 结构体的声明与定义&#xff08;注意定义后面要跟分号来表示结束&#xff09; – 仅有声明的结构体是不完全…

【Linux后端服务器开发】共享内存

目录 一、共享内存概述 二、共享内存&#xff08;IPC资源&#xff09;的查看——ipcm 三、共享内存的创建——shmget 四、共享内存的控制&#xff08;删除&#xff09;——shmctl 五、共享内存的关联——shmat 六、共享内存去关联——shmdt 七、进程间通信 一、共享内存…

力扣 -- 309. 最佳买卖股票时机含冷冻期

题目链接&#xff1a;309. 最佳买卖股票时机含冷冻期 - 力扣&#xff08;LeetCode&#xff09; 下面是用动态规划的思想解决这道题的过程&#xff0c;相信各位小伙伴都能看懂并且掌握这道经典的动规题目滴。 参考代码&#xff1a; class Solution { public:int maxProfit(vec…

基于matlab使用深度神经网络对肿瘤图像进行分类(附源码)

一、前言 此示例演示如何使用 Inception-v3 深度神经网络对可能不适合内存的多分辨率全玻片图像 &#xff08;WSI&#xff09; 进行分类。 用于肿瘤分类的深度学习方法依赖于数字病理学&#xff0c;其中整个组织切片被成像和数字化。生成的 WSI 具有高分辨率&#xff0c;大约…

Openlayers实战:平移、弹性平移、飞行动画

Openlayers地图上经常会遇到这样的一种 场景,获取到某数据后,会重新定位中心点到某个位置,这里可以用setCenter([lon,lat]), 更可以用动画的形式展现。 在本实战中,展示出平移、弹性平移、飞行的动画。 效果图 源代码 /* * @Author: 大剑师兰特(xiaozhuanlan),还是大剑…

【图像处理OpenCV(C++版)】——5.5 图像平滑之双边滤波

前言&#xff1a; &#x1f60a;&#x1f60a;&#x1f60a;欢迎来到本博客&#x1f60a;&#x1f60a;&#x1f60a; &#x1f31f;&#x1f31f;&#x1f31f; 本专栏主要结合OpenCV和C来实现一些基本的图像处理算法并详细解释各参数含义&#xff0c;适用于平时学习、工作快…

第三十六天 Java基础学习(三十)

一、Spring MVC 组件是将处理某类问题的代码进行封装的整体模块。一个大的问题可以拆分为不同的小问题&#xff0c;解决每个小问题的代码封装可以称之为组件&#xff0c;但是组件又是无法独立运行的&#xff0c;必须结合其他组件一起才能最终解决问题。就好比汽车&#xff0c;…

同步任务和异步任务的执行过程

同步任务和异步任务的执行过程 1、执行过程描述2、EventLoop的概念 1、执行过程描述 同步任务 是由JS主线程按次序执行异步任务委托给宿主环境执行已完成的异步任务对应的回调函数&#xff0c;会被加入到任务队列中等待执行JS的主线程的执行栈被清空后&#xff0c;会读取任务队…

Java026——System 类和Scanner 类

一、System 类 1.1、System 类提供的常用方法 方法 功能描述 ----------------------------------------------------------------------------------------------------------------------- currentTimeMillis() 返回当前计算机时间 和 格林威治时间&#xff…

mysql查询练习

1.创建表 CREATE TABLE worker( 部门号 int(11) NOT NULL, 职工号 int(11) NOT NULL, 工作时间 date NOT NULL,工资 float(8,2) NOT NULL,政治面貌 varchar(10) NOT NULL DEFAULT 群众 , 姓名 varchar(20) NOT NULL,出生日期 date NOT NULL,PRIMARY KEY (职工号))ENGINE…

4.3Java EE——一对多查询

用户与订单关联关系图​​​​​​​ 与一对一的关联关系相比&#xff0c;接触更多的关联关系是一对多&#xff08;或多对一&#xff09;。例如一个用户可以有多个订单&#xff0c;多个订单也可以归一个用户所有。用户和订单的关联关系如图。 一、<collection>元素 在MyB…