欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Sping?Security前后端分離兩種實(shí)戰(zhàn)方案

 更新時(shí)間:2023年03月27日 08:21:19   作者:"大魔王先生  
這篇文章主要介紹了Sping?Security前后端分離兩種方案,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

前言

本篇文章是基于Spring Security實(shí)現(xiàn)前后端分離登錄認(rèn)證及權(quán)限控制的實(shí)戰(zhàn),主要包括以下四方面內(nèi)容:

  • Spring Seciruty簡(jiǎn)單介紹;

  • 通過(guò)Spring Seciruty實(shí)現(xiàn)的基于表單和Token認(rèn)證的兩種認(rèn)證方式;

  • 自定義實(shí)現(xiàn)RBAC的權(quán)限控制;

  • 跨域問(wè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)的流程,

在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)題更好的去處理。

Maven

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

????<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)限表;

基于表單認(rèn)證

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

核心配置

核心配置包含了框架開(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ù)查詢對(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ù)用戶名查詢出用戶對(duì)象交由UserDetialsService接口處理,該接口只有一個(gè)方法loadUserByUsername,通過(guò)用戶名查詢用戶對(duì)象。查詢出來(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)有使用緩存。

核心配置

與表單認(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)。

到此這篇關(guān)于Sping Security前后端分離兩種方案的文章就介紹到這了,更多相關(guān)Sping Security前后端分離內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java 調(diào)用天氣Webservice詳解及實(shí)例代碼

    Java 調(diào)用天氣Webservice詳解及實(shí)例代碼

    這篇文章主要介紹了Java 調(diào)用天氣Webservice詳解及實(shí)例代碼的相關(guān)資料,這里附實(shí)例代碼,使用java 調(diào)用webservice 的小應(yīng)用,需要的朋友可以參考下
    2016-11-11
  • 詳解springmvc如何處理接受http請(qǐng)求

    詳解springmvc如何處理接受http請(qǐng)求

    這篇文章主要給大家介紹了springmvc如何處理接受http請(qǐng)求,文中通過(guò)代碼示例給大家講解的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2024-02-02
  • ExecutorService Callable Future多線程返回結(jié)果原理解析

    ExecutorService Callable Future多線程返回結(jié)果原理解析

    這篇文章主要為大家介紹了ExecutorService Callable Future多線程返回結(jié)果,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • CommonMark 使用教程:將 Markdown 語(yǔ)法轉(zhuǎn)成 Html

    CommonMark 使用教程:將 Markdown 語(yǔ)法轉(zhuǎn)成 Html

    這篇文章主要介紹了CommonMark 使用教程:將 Markdown 語(yǔ)法轉(zhuǎn)成 Html,這個(gè)技巧我們做任何網(wǎng)站都可以用到,而且非常好用。,需要的朋友可以參考下
    2019-06-06
  • 淺談Spring Cloud下微服務(wù)權(quán)限方案

    淺談Spring Cloud下微服務(wù)權(quán)限方案

    這篇文章主要介紹了淺談Spring Cloud下微服務(wù)權(quán)限方案,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-06-06
  • spring boot實(shí)現(xiàn)過(guò)濾器和攔截器demo

    spring boot實(shí)現(xiàn)過(guò)濾器和攔截器demo

    本篇文章主要介紹了spring boot實(shí)現(xiàn)過(guò)濾器和攔截器demo ,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-02-02
  • JAVA后端學(xué)習(xí)精華之網(wǎng)絡(luò)通信項(xiàng)目進(jìn)階

    JAVA后端學(xué)習(xí)精華之網(wǎng)絡(luò)通信項(xiàng)目進(jìn)階

    不同項(xiàng)目之間的通信方式分為,http、socket、webservice;其中socket通信的效率最高,youtube就采用的是原始的socket通信,他們信奉的原則是簡(jiǎn)單有效
    2021-09-09
  • Arrays.asList方法總結(jié)

    Arrays.asList方法總結(jié)

    本文主要對(duì)Arrays.asList方法進(jìn)行總結(jié)。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧
    2017-02-02
  • Java的RocketMq隊(duì)列之消息可靠性詳解

    Java的RocketMq隊(duì)列之消息可靠性詳解

    這篇文章主要介紹了Java的RocketMq隊(duì)列之消息可靠性詳解,生產(chǎn)者通過(guò)網(wǎng)絡(luò)發(fā)送消息給 Broker,當(dāng) Broker 收到之后,將會(huì)返回確認(rèn)響應(yīng)信息給 Producer,所以生產(chǎn)者只要接收到返回的確認(rèn)響應(yīng),就代表消息在生產(chǎn)階段未丟失,需要的朋友可以參考下
    2024-01-01
  • Spring Cloud Eureka 服務(wù)上下線監(jiān)控的實(shí)現(xiàn)

    Spring Cloud Eureka 服務(wù)上下線監(jiān)控的實(shí)現(xiàn)

    這篇文章主要介紹了Spring Cloud Eureka 服務(wù)上下線監(jiān)控的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-09-09

最新評(píng)論