溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Sping?Security前后端分離怎么實(shí)現(xiàn)

發(fā)布時(shí)間:2023-03-27 09:51:34 來(lái)源:億速云 閱讀:140 作者:iii 欄目:開(kāi)發(fā)技術(shù)

本篇內(nèi)容主要講解“Sping Security前后端分離怎么實(shí)現(xiàn)”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Sping Security前后端分離怎么實(shí)現(xiàn)”吧!

Spring Seciruty簡(jiǎn)單介紹

Spring Security是基于Spring框架,提供了一套Web應(yīng)用安全性的完整解決方案。關(guān)于安全方面的兩個(gè)核心功能是認(rèn)證和授權(quán),Spring Security重要核心功能就是實(shí)現(xiàn)用戶認(rèn)證(Authentication)和用戶授權(quán)(Authorization)。

認(rèn)證(Authentication)

認(rèn)證是用來(lái)驗(yàn)證某個(gè)用戶能否訪問(wèn)該系統(tǒng)。用戶認(rèn)證一般要求用戶提供用戶名和密碼,系統(tǒng)通過(guò)校驗(yàn)用戶名和密碼來(lái)完成認(rèn)證過(guò)程。

授權(quán)(Authorization)

授權(quán)發(fā)生在認(rèn)證之后,用來(lái)驗(yàn)證某個(gè)用戶是否有權(quán)限執(zhí)行某個(gè)操作。在一個(gè)系統(tǒng)中,不同用戶所具有的權(quán)限是不同的。比如對(duì)一個(gè)文件來(lái)說(shuō),有的用戶只能進(jìn)行讀取,而有的用戶可以進(jìn)行修改。一般來(lái)說(shuō),系統(tǒng)會(huì)為不同的用戶分配不同的角色,而每個(gè)角色則對(duì)應(yīng)一系列的權(quán)限。

實(shí)現(xiàn)簡(jiǎn)單介紹

Spring Security進(jìn)行認(rèn)證和鑒權(quán)的時(shí)候,采用的一系列的Filter來(lái)進(jìn)行攔截的。 下圖是Spring Security基于表單認(rèn)證授權(quán)的流程,

Sping?Security前后端分離怎么實(shí)現(xiàn)

在Spring Security一個(gè)請(qǐng)求想要訪問(wèn)到API就會(huì)從左到右經(jīng)過(guò)藍(lán)線框里的過(guò)濾器,其中綠色部分是負(fù)責(zé)認(rèn)證的過(guò)濾器,藍(lán)色部分是負(fù)責(zé)異常處理,橙色部分則是負(fù)責(zé)授權(quán)。進(jìn)過(guò)一系列攔截最終訪問(wèn)到我們的API。

準(zhǔn)備階段

整個(gè)項(xiàng)目結(jié)構(gòu)如下,demo1部分是基于表單的認(rèn)證,demo2部分是基于Token的認(rèn)證,數(shù)據(jù)庫(kù)采用是Mysql,訪問(wèn)數(shù)據(jù)庫(kù)使用的JPA,Spring Boot版本是2.7.8,Spring Security版本是比較新的5.7.6,這里需要注意的是Spring Security5.7以后版本和前面的版本有一些差異,未來(lái)該Demo的版本的問(wèn)題一直會(huì)持續(xù)保持升級(jí)。 后續(xù)也會(huì)引用前端項(xiàng)目,前端后臺(tái)管理部分我個(gè)人感覺(jué)后端程序員也要進(jìn)行簡(jiǎn)單的掌握一些,便于工作中遇到形形色色問(wèn)題更好的去處理。

Sping?Security前后端分離怎么實(shí)現(xiàn)

Maven

關(guān)于Maven部分細(xì)節(jié)這里不進(jìn)行展示了,采用的父子工程,主要簡(jiǎn)單看下依賴(lài)的版本,

    <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>

統(tǒng)一錯(cuò)誤碼

public enum ResultCode {

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

    /* 默認(rèn)失敗 */
    COMMON_FAIL(999, "失敗"),

    /* 參數(shù)錯(cuò)誤:1000~1999 */
    PARAM_NOT_VALID(1001, "參數(shù)無(wú)效"),
    PARAM_IS_BLANK(1002, "參數(shù)為空"),
    PARAM_TYPE_ERROR(1003, "參數(shù)類(lèi)型錯(cuò)誤"),
    PARAM_NOT_COMPLETE(1004, "參數(shù)缺失"),

    /* 用戶錯(cuò)誤 */
    USER_NOT_LOGIN(2001, "用戶未登錄"),
    USER_ACCOUNT_EXPIRED(2002, "賬號(hào)已過(guò)期"),
    USER_CREDENTIALS_ERROR(2003, "密碼錯(cuò)誤"),
    USER_CREDENTIALS_EXPIRED(2004, "密碼過(guò)期"),
    USER_ACCOUNT_DISABLE(2005, "賬號(hào)不可用"),
    USER_ACCOUNT_LOCKED(2006, "賬號(hào)被鎖定"),
    USER_ACCOUNT_NOT_EXIST(2007, "賬號(hào)不存在"),
    USER_ACCOUNT_ALREADY_EXIST(2008, "賬號(hào)已存在"),
    USER_ACCOUNT_USE_BY_OTHERS(2009, "賬號(hào)下線"),

    /* 業(yè)務(wù)錯(cuò)誤 */
    NO_PERMISSION(3001, "沒(méi)有權(quán)限");
    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);
    }
}

統(tǒng)一返回定義

public class CommonResponse<T> implements Serializable {

    /**
     * 成功狀態(tài)碼
     */
    private final static String SUCCESS_CODE = "SUCCESS";

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

    /**
     * 返回?cái)?shù)據(jù)
     */
    private T data;

    /**
     * 狀態(tài)碼
     */
    private Integer code;

    /**
     * 狀態(tài)
     */
    private Boolean state;

    /**
     * 錯(cuò)誤明細(xì)
     */
    private String detailMessage;


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

    /**
     * 成功
     *
     * @param data 傳入的對(duì)象
     * @param <T>  泛型
     * @return 返回結(jié)果
     */
    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;
    }

    /**
     * 錯(cuò)誤
     *
     * @param code    自定義code
     * @param message 自定義返回信息
     * @param <T>     泛型
     * @return 返回信息
     */
    public static <T> CommonResponse<T> error(Integer code, String message) {
        return error(code, message, null);
    }

    /**
     * 錯(cuò)誤
     *
     * @param code          自定義code
     * @param message       自定義返回信息
     * @param detailMessage 錯(cuò)誤詳情信息
     * @param <T>           泛型
     * @return 返回錯(cuò)誤信息
     */
    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;
    }
}

數(shù)據(jù)庫(kù)設(shè)計(jì)

基于RBAC模型最簡(jiǎn)單奔版本的數(shù)據(jù)庫(kù)設(shè)計(jì),用戶、角色、權(quán)限表;

Sping?Security前后端分離怎么實(shí)現(xiàn)

基于表單認(rèn)證

對(duì)于表單認(rèn)證整體過(guò)程可以參考下圖,

Sping?Security前后端分離怎么實(shí)現(xiàn)

核心配置

核心配置包含了框架開(kāi)啟以及權(quán)限配置,這部分內(nèi)容是重點(diǎn)要關(guān)注的,這里可以看到所有重寫(xiě)的內(nèi)容,主要包含以下七方面內(nèi)容:

  • 定義哪些資源不需要認(rèn)證,哪些需要認(rèn)證,這里我采用注解形式;

  • 實(shí)現(xiàn)自定義認(rèn)證以及授權(quán)異常的接口;

  • 實(shí)現(xiàn)自定義登錄成功以及失敗的接口;

  • 實(shí)現(xiàn)自定義登出以后的接口;

  • 實(shí)現(xiàn)自定義重?cái)?shù)據(jù)查詢(xún)對(duì)應(yīng)的賬號(hào)權(quán)限的接口;

  • 自定義加密的Bean;

  • 自定義授權(quán)認(rèn)證Bean;

當(dāng)然Spring Security還支持更多內(nèi)容,比如限制用戶登錄個(gè)數(shù)等等,這里部分內(nèi)容使用不是太多,后續(xù)大家如果有需要我也可以進(jìn)行補(bǔ)充。

//Spring Security框架開(kāi)啟
@EnableWebSecurity
//授權(quán)全局配置
@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關(guān)閉
                .csrf().disable()
                //配置哪些需要認(rèn)證 哪些不需要認(rèn)證
                .authorizeRequests(rep -> rep.antMatchers(notAuthenticationConfig.getPermitAllUrls().toArray(new String[0]))
                        .permitAll().anyRequest().authenticated())
                .exceptionHandling()
                //認(rèn)證異常處理
                .authenticationEntryPoint(new ResourceAuthExceptionEntryPoint())
                //授權(quán)異常處理
                .accessDeniedHandler(new CustomizeAccessDeniedHandler())
                //登錄認(rèn)證處理
                .and().formLogin()
                .successHandler(new CustomizeAuthenticationSuccessHandler())
                .failureHandler(new CustomizeAuthenticationFailureHandler())
                //登出
                .and().logout().permitAll().addLogoutHandler(new CustomizeLogoutHandler())
                .logoutSuccessHandler(new CustomizeLogoutSuccessHandler())
                .deleteCookies("JSESSIONID")
                //自定義認(rèn)證
                .and().userDetailsService(sysUserService);
        return http.build();
    }


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

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

通過(guò)注解形式實(shí)現(xiàn)哪些需要資源不需要認(rèn)證

通過(guò)自定義注解@NotAuthentication,然后通過(guò)實(shí)現(xiàn)InitializingBean接口,實(shí)現(xiàn)加載不需要認(rèn)證的資源,支持類(lèi)和方法,使用就是通過(guò)在方法或者類(lèi)打上對(duì)應(yīng)的注解。

@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))));

            // 獲取類(lèi)上邊的注解, 替代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;
    }
}

自定義認(rèn)證異常實(shí)現(xiàn)

AuthenticationEntryPoint?用來(lái)解決匿名用戶訪問(wèn)無(wú)權(quán)限資源時(shí)的異常。

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));
    }
}

自定義授權(quán)異常實(shí)現(xiàn)

AccessDeniedHandler用來(lái)解決認(rèn)證過(guò)的用戶訪問(wèn)無(wú)權(quán)限資源時(shí)的異常。

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中返回給前臺(tái)
        response.getWriter().write(JSON.toJSONString(result));
    }
}

自定義登錄成功、失敗

AuthenticationSuccessHandler和AuthenticationFailureHandler這兩個(gè)接口用于登錄成功失敗以后的處理。

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數(shù)據(jù)
        CommonResponse<AuthUser> result = CommonResponse.ok(authUser);
        //處理編碼方式,防止中文亂碼的情況
        response.setContentType("text/json;charset=utf-8");
        //塞到HttpServletResponse中返回給前臺(tái)
        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數(shù)據(jù)
        CommonResponse result = null;
        if (exception instanceof AccountExpiredException) {
            //賬號(hào)過(guò)期
            result = CommonResponse.error(ResultCode.USER_ACCOUNT_EXPIRED.getCode(), ResultCode.USER_ACCOUNT_EXPIRED.getMessage());
        } else if (exception instanceof BadCredentialsException) {
            //密碼錯(cuò)誤
            result = CommonResponse.error(ResultCode.USER_CREDENTIALS_ERROR.getCode(), ResultCode.USER_CREDENTIALS_ERROR.getMessage());
//        } else if (exception instanceof CredentialsExpiredException) {
//            //密碼過(guò)期
//            result = CommonResponse.error(ResultCode.USER_CREDENTIALS_EXPIRED);
//        } else if (exception instanceof DisabledException) {
//            //賬號(hào)不可用
//            result = CommonResponse.error(ResultCode.USER_ACCOUNT_DISABLE);
//        } else if (exception instanceof LockedException) {
//            //賬號(hào)鎖定
//            result = CommonResponse.error(ResultCode.USER_ACCOUNT_LOCKED);
//        } else if (exception instanceof InternalAuthenticationServiceException) {
//            //用戶不存在
//            result = CommonResponse.error(ResultCode.USER_ACCOUNT_NOT_EXIST);
        } else {
            //其他錯(cuò)誤
            result = CommonResponse.error(ResultCode.COMMON_FAIL.getCode(), ResultCode.COMMON_FAIL.getMessage());
        }
        //處理編碼方式,防止中文亂碼的情況
        response.setContentType("text/json;charset=utf-8");
        //塞到HttpServletResponse中返回給前臺(tái)
        response.getWriter().write(JSON.toJSONString(result));
    }
}

自定義登出

LogoutHandler自定義登出以后處理邏輯,比如記錄在線時(shí)長(zhǎng)等等;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) {

    }
}

自定義認(rèn)證

自定義認(rèn)證涉及三個(gè)對(duì)象UserDetialsService、UserDetails以及PasswordEncoder,整個(gè)流程首先根據(jù)用戶名查詢(xún)出用戶對(duì)象交由UserDetialsService接口處理,該接口只有一個(gè)方法loadUserByUsername,通過(guò)用戶名查詢(xún)用戶對(duì)象。查詢(xún)出來(lái)的用戶對(duì)象需要通過(guò)Spring Security中的用戶數(shù)據(jù)UserDetails實(shí)體類(lèi)來(lái)體現(xiàn),這里使用AuthUser繼承User,User本質(zhì)上就是繼承與UserDetails,UserDetails該類(lèi)中提供了賬號(hào)、密碼等通用屬性。對(duì)密碼進(jìn)行校驗(yàn)使用PasswordEncoder組件,負(fù)責(zé)密碼加密與校驗(yàn)。

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認(rèn)證

基于Token認(rèn)證這里我采用JWT方式,下圖是整個(gè)處理的流程,通過(guò)自定義的登錄以及JwtAuthenticationTokenFilter來(lái)完成整個(gè)任務(wù)的實(shí)現(xiàn),需要注意的是這里我沒(méi)有使用緩存。

Sping?Security前后端分離怎么實(shí)現(xiàn)

核心配置

與表單認(rèn)證不同的是這里關(guān)閉和FormLogin表單認(rèn)證以及不使用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關(guān)閉
                .csrf().disable()
                //不使用session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().authorizeRequests(rep -> rep.antMatchers(notAuthenticationConfig.getPermitAllUrls().toArray(new String[0]))
                        .permitAll().anyRequest().authenticated())
                .exceptionHandling()
                //異常認(rèn)證
                .authenticationEntryPoint(new ResourceAuthExceptionEntryPoint())
                .accessDeniedHandler(new CustomizeAccessDeniedHandler())
                .and()
                //token過(guò)濾
                .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創(chuàng)建

@Service
public class LoginService {


    @Autowired
    private AuthenticationManager authenticationManager ;

    @Autowired
    private SysUserService sysUserService;


    public LoginVO login(LoginDTO loginDTO) {
        //創(chuàng)建Authentication對(duì)象
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginDTO.getUsername(),
                loginDTO.getPassword());

        //調(diào)用AuthenticationManager的authenticate方法進(jìn)行認(rèn)證
        Authentication authentication = authenticationManager.authenticate(authenticationToken);

        if(authentication == null) {
            throw new RuntimeException("用戶名或密碼錯(cuò)誤");
        }
        //登錄成功以后用戶信息、
        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("認(rèn)證失敗");
        }
        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("認(rèn)證失敗");
    }
}

Token過(guò)濾

public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        //check Token
        if (checkJWTToken(request)) {
            //解析token中的認(rèn)證信息
            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;
    }
}

授權(quán)處理

全局授權(quán)的配置已經(jīng)在核心配置中開(kāi)啟,核心思路是通過(guò)SecurityContextHolder獲取當(dāng)前用戶權(quán)限,判斷當(dāng)前用戶的權(quán)限是否包含該方法的權(quán)限,此部分設(shè)計(jì)后續(xù)如果存在性能問(wèn)題,可以設(shè)計(jì)緩存來(lái)解決。

授權(quán)檢查

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";
}

跨域問(wèn)題處理

關(guān)于這部分跨域部分的配置還可以更加細(xì)化一點(diǎn)。

@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 設(shè)置允許跨域的路徑
        registry.addMapping("/**")
                // 設(shè)置允許跨域請(qǐng)求的域名
                .allowedOriginPatterns("*")
                // 是否允許cookie
                .allowCredentials(true)
                // 設(shè)置允許的請(qǐng)求方式
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                // 設(shè)置允許的header屬性
                .allowedHeaders("*")
                // 跨域允許時(shí)間
                .maxAge(3600);
    }
}

vue-admin-template登錄的簡(jiǎn)單探索感悟

這部分就是有些感悟(背景自身是沒(méi)有接觸過(guò)Vue相關(guān)的知識(shí)),具體的感悟就是不要畏懼一些自己不知道以及不會(huì)的東西,大膽的去嘗試,因?yàn)樽陨淼臐摿κ呛艽蟮?。為什么要這么講,通過(guò)自己折騰3個(gè)小時(shí),自己完成整個(gè)登錄過(guò)程,如果是前端可能會(huì)比較簡(jiǎn)單,針對(duì)我這種從沒(méi)接觸過(guò)的還是有些難度的,需要一些基礎(chǔ)配置更改以及流程梳理,這里簡(jiǎn)單來(lái)讓大家看下效果,后續(xù)我也會(huì)自己把剩下菜單動(dòng)態(tài)加載以及一些簡(jiǎn)單表單交互來(lái)完成,做到簡(jiǎn)單的表單可以自己來(lái)實(shí)現(xiàn)。

Sping?Security前后端分離怎么實(shí)現(xiàn)

Sping?Security前后端分離怎么實(shí)現(xiàn)

到此,相信大家對(duì)“Sping Security前后端分離怎么實(shí)現(xiàn)”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI