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>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<Timplements 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模型最簡單奔版本的數據庫設計,用戶、角色、權限表; 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 InitializingBeanApplicationContextAware {

    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來完成整個任務的實現,需要注意的是這裏我沒有使用緩存。 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 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個小時,自己完成整個登錄過程,如果是前端可能會比較簡單,針對我這種從沒接觸過的還是有些難度的,需要一些基礎配置更改以及流程梳理,這裏簡單來讓大家看下效果,後續我也會自己把剩下菜單動態加載以及一些簡單表單交互來完成,做到簡單的表單可以自己來實現。 image.png image.png

結束

歡迎大家點點關注,點點贊! 具體代碼可參考github

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章