• Sping Security前后端分离两种方案


    前言

    本篇文章是基于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基于表单认证授权的流程, image.png 在Spring Security一个请求想要访问到API就会从左到右经过蓝线框里的过滤器,其中绿色部分是负责认证的过滤器,蓝色部分是负责异常处理,橙色部分则是负责授权。进过一系列拦截最终访问到我们的API。

    准备阶段

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

    Maven

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

        <properties>
            <maven.compiler.source>8maven.compiler.source>
            <maven.compiler.target>8maven.compiler.target>
            <springboot.vetsion>2.7.8springboot.vetsion>
            <mysql-connector-java.version>5.1.46mysql-connector-java.version>
            <org.projectlombok.version>1.16.14org.projectlombok.version>
            <jjwt.version>0.11.1jjwt.version>
            <fastjson.version>1.2.75fastjson.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<IntegerResultCode> map = new HashMap<>();
        private static Map<StringResultCode> 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 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  泛型
         * @return 返回结果
         */
        public static  CommonResponse ok() {
            return ok(null);
        }
    
        /**
         * 成功
         *
         * @param data 传入的对象
         * @param   泛型
         * @return 返回结果
         */
        public static  CommonResponse ok(T data) {
            CommonResponse response = new CommonResponse();
            response.code = ResultCode.SUCCESS.getCode();
            response.data = data;
            response.message = "返回成功";
            response.state = true;
            return response;
        }
    
        /**
         * 错误
         *
         * @param code    自定义code
         * @param message 自定义返回信息
         * @param      泛型
         * @return 返回信息
         */
        public static  CommonResponse error(Integer code, String message) {
            return error(code, message, null);
        }
    
        /**
         * 错误
         *
         * @param code          自定义code
         * @param message       自定义返回信息
         * @param detailMessage 错误详情信息
         * @param            泛型
         * @return 返回错误信息
         */
        public static  CommonResponse error(Integer code, String message,
                                                  String detailMessage) {
            CommonResponse response = new CommonResponse();
            response.code = code;
            response.data = null;
            response.message = message;
            response.state = false;
            response.detailMessage = detailMessage;
            return response;
        }
    
        public Boolean getState() {
            return state;
        }
    
        public CommonResponse setState(Boolean state) {
            this.state = state;
            return this;
        }
    
        public String getMessage() {
            return message;
        }
    
        public CommonResponse setMessage(String message) {
            this.message = message;
            return this;
        }
    
        public T getData() {
            return data;
        }
    
        public CommonResponse setData(T data) {
            this.data = data;
            return this;
        }
    
        public Integer getCode() {
            return code;
        }
    
        public CommonResponse setCode(Integer code) {
            this.code = code;
            return this;
        }
    
        public String getDetailMessage() {
            return detailMessage;
        }
    
        public CommonResponse setDetailMessage(String detailMessage) {
            this.detailMessage = detailMessage;
            return this;
        }
    }
    

    数据库设计

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

    基于表单认证

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

    核心配置

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

    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 permitAllUrls = new ArrayList<>();
    
        @Override
        public void afterPropertiesSet() throws Exception {
            RequestMappingHandlerMapping mapping = applicationContext.getBean(RequestMappingHandlerMapping.class);
            Map 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 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, Collectionextends 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来完成整个任务的实现,需要注意的是这里我没有使用缓存。 image.png

    核心配置

    与表单认证不同的是这里关闭和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 ServletExceptionIOException {
            //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  List castList(Object obj, Class clazz) {
            List result = new ArrayList();
            if (obj instanceof List) {
                for (Object o : (List) obj) {
                    result.add(clazz.cast(o));
                }
                return result;
            }
            return null;
        }
    
        private Optional<ClaimsvalidateToken(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;
            }
            Collectionextends 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;
            }
            Collectionextends 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个小时,自己完成整个登录过程,如果是前端可能会比较简单,针对我这种从没接触过的还是有些难度的,需要一些基础配置更改以及流程梳理,这里简单来让大家看下效果,后续我也会自己把剩下菜单动态加载以及一些简单表单交互来完成,做到简单的表单可以自己来实现。 image.png image.png

    结束

    欢迎大家点点关注,点点赞! 具体代码可参考github

  • 相关阅读:
    格式化以后数据还在吗 格式化后数据怎么恢复
    常用类学习(数字类、随机数、枚举详解)
    外包干了5天,技术明显退步。。。。。
    故障分析 | 从 data_free 异常说起
    你的知识库能提高工作效率的7个原因
    酷安官网下载页前端自适应源码
    [附源码]计算机毕业设计springboot项目管理系统的专家评审模块
    通过 Docker 灵活部署 Neo4j 图数据库
    三防加固平板电脑能在恶劣的工业环境中依然正常运作
    5、JAVA入门——变量和数据类型1
  • 原文地址:https://www.cnblogs.com/wtzbk/p/17260243.html