前言
本篇文章是基於Spring Security實現前後端分離登錄認證及權限控制的實戰,主要包括以下四方面內容:
Spring Seciruty簡單介紹; 通過Spring Seciruty實現的基於表單和Token認證的兩種認證方式; 自定義實現RBAC的權限控制; 跨域問題處理;
Spring Seciruty簡單介紹
Spring Security是基於Spring框架,提供了一套Web應用安全性的完整解決方案。關於安全方面的兩個核心功能是認證和授權,Spring Security重要核心功能就是實現用戶認證(Authentication)和用戶授權(Authorization)。
認證(Authentication)
認證是用來驗證某個用戶能否訪問該系統。用戶認證一般要求用戶提供用戶名和密碼,系統通過校驗用戶名和密碼來完成認證過程。
授權(Authorization)
授權發生在認證之後,用來驗證某個用戶是否有權限執行某個操作。在一個系統中,不同用戶所具有的權限是不同的。比如對一個文件來說,有的用戶只能進行讀取,而有的用戶可以進行修改。一般來說,系統會爲不同的用戶分配不同的角色,而每個角色則對應一系列的權限。
實現簡單介紹
Spring Security進行認證和鑑權的時候,採用的一系列的Filter來進行攔截的。 下圖是Spring Security基於表單認證授權的流程, 在Spring Security一個請求想要訪問到API就會從左到右經過藍線框裏的過濾器,其中綠色部分是負責認證的過濾器,藍色部分是負責異常處理,橙色部分則是負責授權。進過一系列攔截最終訪問到我們的API。
準備階段
整個項目結構如下,demo1部分是基於表單的認證,demo2部分是基於Token的認證,數據庫採用是Mysql,訪問數據庫使用的JPA,Spring Boot版本是2.7.8,Spring Security版本是比較新的5.7.6,這裏需要注意的是Spring Security5.7以後版本和前面的版本有一些差異,未來該Demo的版本的問題一直會持續保持升級。 後續也會引用前端項目,前端後臺管理部分我個人感覺後端程序員也要進行簡單的掌握一些,便於工作中遇到形形色色問題更好的去處理。
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<T> implements Serializable {
/**
* 成功狀態碼
*/
private final static String SUCCESS_CODE = "SUCCESS";
/**
* 提示信息
*/
private String message;
/**
* 返回數據
*/
private T data;
/**
* 狀態碼
*/
private Integer code;
/**
* 狀態
*/
private Boolean state;
/**
* 錯誤明細
*/
private String detailMessage;
/**
* 成功
*
* @param <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模型最簡單奔版本的數據庫設計,用戶、角色、權限表;
基於表單認證
對於表單認證整體過程可以參考下圖,
核心配置
核心配置包含了框架開啓以及權限配置,這部分內容是重點要關注的,這裏可以看到所有重寫的內容,主要包含以下七方面內容:
定義哪些資源不需要認證,哪些需要認證,這裏我採用註解形式; 實現自定義認證以及授權異常的接口; 實現自定義登錄成功以及失敗的接口; 實現自定義登出以後的接口; 實現自定義重數據查詢對應的賬號權限的接口; 自定義加密的Bean; 自定義授權認證Bean;
當然Spring Security還支持更多內容,比如限制用戶登錄個數等等,這裏部分內容使用不是太多,後續大家如果有需要我也可以進行補充。
//Spring Security框架開啓
@EnableWebSecurity
//授權全局配置
@EnableGlobalMethodSecurity(prePostEnabled = true)
@Configuration
public class SecurityConfig {
@Autowired
private SysUserService sysUserService;
@Autowired
private NotAuthenticationConfig notAuthenticationConfig;
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//支持跨域
http.cors().and()
//csrf關閉
.csrf().disable()
//配置哪些需要認證 哪些不需要認證
.authorizeRequests(rep -> rep.antMatchers(notAuthenticationConfig.getPermitAllUrls().toArray(new String[0]))
.permitAll().anyRequest().authenticated())
.exceptionHandling()
//認證異常處理
.authenticationEntryPoint(new ResourceAuthExceptionEntryPoint())
//授權異常處理
.accessDeniedHandler(new CustomizeAccessDeniedHandler())
//登錄認證處理
.and().formLogin()
.successHandler(new CustomizeAuthenticationSuccessHandler())
.failureHandler(new CustomizeAuthenticationFailureHandler())
//登出
.and().logout().permitAll().addLogoutHandler(new CustomizeLogoutHandler())
.logoutSuccessHandler(new CustomizeLogoutSuccessHandler())
.deleteCookies("JSESSIONID")
//自定義認證
.and().userDetailsService(sysUserService);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
@Bean("ssc")
public SecuritySecurityCheckService permissionService() {
return new SecuritySecurityCheckService();
}
}
通過註解形式實現哪些需要資源不需要認證
通過自定義註解@NotAuthentication,然後通過實現InitializingBean接口,實現加載不需要認證的資源,支持類和方法,使用就是通過在方法或者類打上對應的註解。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
public @interface NotAuthentication {
}
@Service
public class NotAuthenticationConfig implements InitializingBean, ApplicationContextAware {
private static final String PATTERN = "\\{(.*?)}";
public static final String ASTERISK = "*";
private ApplicationContext applicationContext;
@Getter
@Setter
private List<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來完成整個任務的實現,需要注意的是這裏我沒有使用緩存。
核心配置
與表單認證不同的是這裏關閉和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個小時,自己完成整個登錄過程,如果是前端可能會比較簡單,針對我這種從沒接觸過的還是有些難度的,需要一些基礎配置更改以及流程梳理,這裏簡單來讓大家看下效果,後續我也會自己把剩下菜單動態加載以及一些簡單表單交互來完成,做到簡單的表單可以自己來實現。
結束
歡迎大家點點關注,點點贊! 具體代碼可參考github