您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“Sping Security前后端分離怎么實(shí)現(xiàn)”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“Sping Security前后端分離怎么實(shí)現(xià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)證是用來(lái)驗(yàn)證某個(gè)用戶能否訪問(wèn)該系統(tǒng)。用戶認(rèn)證一般要求用戶提供用戶名和密碼,系統(tǒng)通過(guò)校驗(yàn)用戶名和密碼來(lái)完成認(rèn)證過(guò)程。
授權(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)限。
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。
整個(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)題更好的去處理。
關(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>
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); } }
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; } }
基于RBAC模型最簡(jiǎn)單奔版本的數(shù)據(jù)庫(kù)設(shè)計(jì),用戶、角色、權(quá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ù)查詢(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ò)自定義注解@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; } }
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)); } }
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)證涉及三個(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)證這里我采用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(); } }
@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)證失敗"); } }
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)的配置已經(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)解決。
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"; }
關(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); } }
這部分就是有些感悟(背景自身是沒(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)。
到此,相信大家對(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í)!
免責(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)容。