开发者

Sping Security前后端分离两种实战方案

目录
  • 前言
  • Spring Seciruty简单介绍
  • 认证(Aandroiduthentication)
  • 授权(Authorization)
  • 实现简单介绍
  • 准备阶段
  • Maven
  • 统一错误码
  • 统一返回定义
  • 数据库设计
  • 基于表单认证
  • 核心配置
  • 通过注解形式实现哪些需要资源不需要认证
  • 自定义认证异常实现
  • 自定义授权异常实现
  • 自定义登录成功、失败
  • 自定义登出
  • 自定义认证
  • 基于Token认证
  • 核心配置
  • Token创建
  • Token过滤
  • 授权处理
  • 授权检查
  • 如何使用
  • 跨域问题处理
  • vue-admin-template登录的简单探索感悟

前言

本篇文章是基于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基于表单认证授权的流程,

Sping Security前后端分离两种实战方案

在Spring Security一个请求想要访问到API就会从左到右经过蓝线框里的过滤器,其中绿色部分是负责认证的过滤器,蓝色部分是负责异常处理,橙色部分则是负责授权。进过一系列拦截最终访问到我们的API。

准备阶段

整个项目结构如下,demo1部分是基于表单的认证,demo2部分是基于Token的认证,数据库采用是mysql,访问数据库使用的JPA,Spring Boot版本是2.7.8,Spring Security版本是比较新的5.7.6,这里需要注意的是Spring Security5.7以后版本和前面的版本有一些差异,未来该Demo的版本的问题一直会持续保持升级。 后续也会引用前端项目,前端后台管理部分我个人感觉后端程序员也要进行简单的掌握一些,便于工作中遇到形形色色问题更好的去处理。

Sping Security前后端分离两种实战方案

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>

统一错误码

publicenumResultCode{

/*成功*/
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,"没有权限");
privateIntegercode;
privateStringmessage;

ResultCode(Integercode,Stringmessage){
this.code=code;
this.message=message;
}

publicIntegergetCo编程客栈de(){
returncode;
}

publicvoidsetCode(Integercode){
this.code=code;
}

publicStringgetMessage(){
returnmessage;
}

publicvoidsetMessage(Stringmessage){
this.message=message;
}

privatestaticMap<Integer,ResultCode>map=newHashMap<>();
privatestaticMap<String,ResultCode>descMap=newHashMap<>();


static{
for(ResultCodevalue:ResultCode.values()){
map.put(value.getCode(),value);
descMap.put(value.getMessage(),value);
}
}

publicstaticResultCodegetByCode(Integercode){
returnmap.get(code);
}

publicstaticResultCodegetByMessage(Stringdesc){
returndescMap.get(desc);
}
}

统一返回定义

publicclassCommonResponse<T>implementsSerializable{

/**
*成功状态码
*/
privatefinalstaticStringSUCCESS_CODE="SUCCESS";

/**
*提示信息
*/
privateStringmessage;

/**
*返回数据
*/
privateTdata;

/**
*状态码
*/
privateIntegercode;

/**
*状态
*/
privateBooleanstate;

/**
*错误明细
*/
privateStringdetailMessage;


/**
*成功
*
*@param<T>泛型
*@return返回结果
*/
publicstatic<T>CommonResponse<T>ok(){
returnok(null);
}

/**
*成功
*
*@paramdata传入的对象
*@param<T>泛型
*@return返回结果
*/
publicstatic<T>CommonResponse<T>ok(Tdata){
CommonResponse<T>response=newCommonResponse<T>();
response.code=ResultCode.SUCCESS.getCode();
response.data=data;
response.message="返回成功";
response.state=true;
returnresponse;
}

/**
*错误
*
*@paramcode自定义code
*@parammessage自定义返回信息
*@param<T>泛型
*@return返回信息
*/
publicstatic<T>CommonResponse<T>error(Integercode,Stringmessage){
returnerror(code,message,null);
}

/**
*错误
*
*@paramcode自定义code
*@parammessage自定义返回信息
*@paramdetailMessage错误详情信息
*@param<T>泛型
*@return返回错误信息
*/
publicstatic<T>CommonResponse<T>error(Integercode,Stringmessage,
StringdetailMessage){
CommonResponse<T>response=newCommonResponse<T>();
response.code=code;
response.data=null;
response.message=message;
response.state=false;
response.detailMessage=detailMessage;
returnresponse;
}

publicBooleangetState(){
returnstate;
}

publicCommonResponse<T>setState(Booleanstate){
this.state=state;
returnthis;
}

publicStringgetMessage(){
returnmessage;
}

publicCommonResponse<T>setMessage(Stringmessage){
this.message=message;
returnthis;
}

publicTgetData(){
returndata;
}

publicCommonResponse<T>setData(Tdata){
this.data=data;
returnthis;
}

publicIntegergetCode(){
returncode;
}

publicCommonResponse<T>setCode(Integercode){
this.code=code;
returnthis;
}

publicStringgetDetailMessage(){
returndetailMessage;
}

publicCommonResponse<T>setDetailMessage(StringdetailMessage){
this.detailMessage=detailMessage;
returnthis;
}
}

数据库设计

基于RBAC模型最简单奔版本的数据库设计,用户、角色、权限表;

Sping Security前后端分离两种实战方案

基于表单认证

对于表单认证整体过程可以参考下图,

Sping Security前后端分离两种实战方案

核心javascript配置

核心配置包含了框架开启以及权限配置,这部分内容是重点要关注的,这里可以看到所有重写的内容,主要包含以下七方面内容:

  • 定义哪些资源不需要认证,哪些需要认证,这里我采用注解形式;

  • 实现自定义认证以及授权异常的接口;

  • 实现自定义登录成功以及失败的接口;

  • 实现自定义登出以后的接口;

  • 实现自定义重数据查询对应的账号权限的接口;

  • 自定义加密的Bean;

  • 自定义授权认证Bean;

当然Spring Security还支持更多内容,比如限制用户登录个数等等,这里部分内容使用不是太多,后续大家如果有需要我也可以进行补充。

//SpringSecurity框架开启
@EnableWebSecurity
//授权全局配置
@EnableGlobalMethodSecurity(prePostEnabled=true)
@Configuration
publicclassSecurityConfig{

@Autowired
privateSysUserServicesysUserService;

@Autowired
privateNotAuthenticationConfignotAuthenticationConfig;


@Bean
SecurityFilterChainfilterChain(HttpSecurityhttp)throwsException{
//支持跨域
http.cors().and()
//csrf关闭
.csrf().disable()
//配置哪些需要认证哪些不需要认证
.authorizeRequests(rep->rep.antMatchers(notAuthenticationConfig.getPermitAllUrls().toArray(newString[0]))
.permitAll().anyRequest().authenticated())
.exceptionHandling()
//认证异常处理
.authenticationEntryPoint(newResourceAuthExceptionEntryPoint())
//授权异常处理
.AccessDeniedHandler(newCustomizeAccessDeniedHandler())
//登录认证处理
.and().formLogin()
.successHandler(newCustomizeAuthenticationSuccessHandler())
.failureHandler(newCustomizeAuthenticationFailureHandler())
//登出
.and().logout().permitAll().addLogoutHandler(newCustomizeLogoutHandler())
.logoutSuccessHandler(newCustomizeLogoutSuccessHandler())
.deleteCookies("JSESSIONID")
//自定义认证
.and().userDetailsService(sysUserService);
returnhttp.build();
}


@Bean
publicPasswordEncoderpasswordEncoder(){
BCryptPasswordEncoderbCryptPasswordEncoder=newBCryptPasswordEncoder();
returnbCryptPasswordEncoder;
}

@Bean("ssc")
publicSecuritySecurityCheckServicepermissionService(){
returnnewSecuritySecurityCheckService();
}

}

通过注解形式实现哪些需要资源不需要认证

通过自定义注解@NotAuthentication,然后通过实现InitializingBean接口,实现加载不需要认证的资源,支持类和方法,使用就是通过在方法或者类打上对应的注解。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public@interfaceNotAuthentication{
}

@Service
publicclassNotAuthenticationConfigimplementsInitializingBean,ApplicationContextAware{

privatestaticfinalStringPATTERN="\\{(.*?)}";

publicstaticfinalStringASTERISK="*";


privateApplicationContextapplicationContext;

@Getter
@Setter
privateList<String>permitAllUrls=newArrayList<>();

@Override
publicvoidafterPropertiesSet()throwsException{
RequestMappingHandlerMappingmapping=applicationContext.getBean(RequestMappingHandlerMapping.class);
Map<RequestMappingInfo,HandlerMethod>map=mapping.getHandlerMethods();
map.keySet().forEach(x->{
HandlerMethodhandlerMethod=map.get(x);

//获取方法上边的注解替代pathvariable为*
NotAuthenticationmethod=AnnotationUtils.findAnnotation(handlerMethod.getMethod(),NotAuthentication.class);
Optional.ofNullable(method).ifPresent(inner->Objects.requireNonNull(x.getPathPatternsCondition())
.getPatternValues().forEach(url->permitAllUrls.add(url.replaceAll(PATTERN,ASTERISK))));

//获取类上边的注解,替代pathvariable为*
NotAuthenticationcontroller=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
publicvoidsetApplicationContext(ApplicationContextapplicationContext)throwsBeansException{
this.applicationContext=applicationContext;
}
}

自定义认证异常实现

AuthenticationEntryPoint�用来解决匿名用户访问无权限资源时的异常。

publicclassResourceAuthExceptionEntryPointimplementsAuthenticationEntryPoint{
@Override
publicvoidcommence(HttpServletRequestrequest,HttpServletResponseresponse,AuthenticationExceptionauthException)throwsIOException,ServletException{
CommonResponseresult=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用来解决认证过的用户访问无权限资源时的异常。

publicclassCustomizeAccessDeniedHandlerimplementsAccessDeniedHandler{
@Override
publicvoidhandle(HttpServletRequestrequest,HttpServletResponseresponse,AccessDeniedExceptionaccessDeniedException)throwsIOException,ServletException{
CommonResponseresult=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这两个接口用于登录成功失败以后的处理。

publicclassCustomizeAuthenticationSuccessHandlerimplementsAuthenticationSuccessHandler{
@Override
publicvoidonAuthenticationSuccess(HttpServletRequestrequest,HttpServletResponseresponse,Authenticationauthentication)throwsIOException,ServletException{
AuthUserauthUser=(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));
}
}

publicclassCustomizeAuthenticationFailureHandlerimplementsAuthenticationFailureHandler{
@Override
publicvoidonAuthenticationFailure(HttpServletRequestrequest,HttpServletResponseresponse,AuthenticationExceptionexception)throwsIOException,ServletException{
//返回json数据
CommonResponseresult=null;
if(exceptioninstanceofAccountExpiredException){
//账号过期
result=CommonResponse.error(ResultCode.USER_ACCOUNT_EXPIRED.getCode(),ResultCode.USER_ACCOUNT_EXPIRED.getMessage());
}elseif(exceptioninstanceofBadCredentialsException){
//密码错误
result=CommonResponse.error(ResultCode.USER_CREDENTIALS_ERROR.getCode(),ResultCode.USER_CREDENTIALS_ERROR.getMessage());
//}elseif(exceptioninstanceofCredentialsExpiredException){
////密码过期
//result=CommonResponse.error(ResultCode.USER_CREDENTIALS_EXPIRED);
//}elseif(exceptioninstanceofDisabledException){
////账号不可用
//result=CommonResponse.error(ResultCode.USER_ACCOUNT_DISABLE);
//}elseif(exceptioninstanceofLockedException){
////账号锁定
//result=CommonResponse.error(ResultCode.USER_ACCOUNT_LOCKED);
//}elseif(exceptioninstanceofInternalAuthenticationServiceException){
////用户不存在
//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登出成功以后逻辑处理。

publicclassCustomizeLogoutSuccessHandlerimplementsLogoutSuccessHandler{
@Override
publicvoidonLogoutSuccess(HttpServletRequestrequest,HttpServletResponseresponse,Authenticationauthentication)throwsIOException,ServletException{

CommonResponseresult=CommonResponse.ok();
response.setContentType("text/json;charset=utf-8");
response.getWriter().write(JSON.toJSONString(result));
}
}

publicclassCustomizeLogoutHandlerimplementsLogoutHandler{

@Override
publicvoidlogout(HttpServletRequestrequest,HttpServletResponseresponse,Authenticationauthentication){

}
}

自定义认证

自定义认证涉及三个对象UserDetialsService、UserDetails以及PasswordEncoder,整个流程首先根据用户名查询出用户对象交由UserDetialsService接口处理,该接口只有一个方法loadUserByUsername,通过用户名查询用户对象。查询出来的用户对象需要通过Spring Security中的用户数据UserDetails实体类来体现,这里使用AuthUser继承User,User本质上就是继承与UserDetails,UserDetails该类中提供了账号、密码等通用属性。对密码进行校验使用PasswordEncoder组件,负责密码加密与校验。

publicclassAuthUserextendsUser{

publicAuthUser(Stringusername,Stringpassword,Collection<?extendsGrantedAuthority>authorities){
super(username,password,authorities);
}
}

@Service
publicclassSysUserServiceimplementsUserDetailsService{

@Autowired
privateSysUserRepositorysysUserRepository;

@Autowired
privateSysRoleServicesysRoleService;

@Autowired
privateSysMenuServicesysMenuService;


@Override
publicUserDetailsloadUserByUsername(Stringusername)throwsUsernameNotFoundException{
Optional<SysUser>sysUser=Optional.ofNullable(sysUserRepository.findOptionalByUsername(username).orElseThrow(()->
newUsernameNotFoundException("未找到用户名")));
List<SysRole>roles=sysRoleService.queryByUserName(sysUser.get().getUsername());
Set<String>dbAuthsSet=newHashSet<>();
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(newString[0]));
returnnewAuthUser(username,sysUser.get().getPassword(),authorities);
}
}

基于Token认证

基于Token认证这里我采用JWT方式,下图是整个处理的流程,通过自定义的登录以及JwtAuthenticationTokenFilter来完成整个任务的实现,需要注意的是这里我没有使用缓存。

Sping Security前后端分离两种实战方案

核心配置

与表单认证不同的是这里关闭和FormLogin表单认证以及不使用Session方式,增加了JwtAuthenticationTokenFilter,此外ResourceAuthExceptionEntryPoint兼职处理之前登录失败以后的异常处理。

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
@Configuration
publicclassSecurityConfig{

@Autowired
privateSysUserServicesysUserService;

@Autowired
privateNotAuthenticationConfignotAuthenticationConfig;
@Bean
SecurityFilterChainfilterChain(HttpSecurityhttp)throwsException{
//支持跨域
http.cors().and()
//csrf关闭
.csrf().disable()
//不使用session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests(rep->rep.antMatchers(notAuthenticationConfig.getPermitAllUrls().toArray(newString[0]))
.permitAll().anyRequest().authenticated())
.exceptionHandling()
//异常认证
.authenticationEntryPoint(newResourceAuthExceptionEntryPoint())
.accessDeniedHandler(newCustomizeAccessDeniedHandler())
.and()
//token过滤
.addFilterBefore(newJwtAuthenticationTokenFilter(),UsernamePasswordAuthenticationFilter.class)
.userDetailsService(sysUserService);
returnhttp.build();
}


/**
*获取AuthenticationManager
*
*@paramconfiguration
*@return
*@throwsException
*/
@Bean
publicAuthenticationManagerauthenticationManager(AuthenticationConfigurationconfiguration)throwsException{
returnconfiguration.getAuthenticationManager();
}

/**
*密码
*
*@return
*/
@Bean
publicPasswordEncoderpasswordEncoder(){
BCryptPasswordEncoderbCryptPasswordEncoder=newBCryptPasswordEncoder();
returnbCryptPasswordEncoder;
}

@Bean("ssc")
publicSecuritySecurityCheckServicepermissionService(){
returnnewSecuritySecurityCheckService();
}

}

Token创建

@Service
publicclassLoginService{


@Autowired
privateAuthenticationManagerauthenticationManager;

@Autowired
privateSysUserServicesysUserService;


publicLoginVOlogin(LoginDTOloginDTO){
//创建Authentication对象
UsernamePasswordAuthenticationTokenauthenticationToken=
newUsernamePasswordAuthenticationToken(loginDTO.getUsername(),
loginDTO.getPassword());

//调用AuthenticationManager的authenticate方法进行认证
Authenticationauthentication=authenticationManager.authenticate(authenticationToken);

if(authentication==null){
thrownewRuntimeException("用户名或密码错误");
}
//登录成功以后用户信息、
AuthUserauthUser=(AuthUser)authentication.getPrincipal();

LoginVOloginVO=newLoginVO();
loginVO.setUserName(authUser.getUsername());
loginVO.setAccessToken(JwtUtils.createAccessToken(authUser));
loginVO.setRefreshToken(JwtUtils.createRefreshToken(authUser));

returnloginVO;
}


publicLoginVOrefreshToken(StringaccessToken,StringrefreshToken){
if(!JwtUtil开发者_JS教程s.validateRefreshToken(refreshToken)&&!JwtUtils.validateWithoutExpiration(accessToken)){
thrownewRuntimeException("认证失败");
}
Optional<String>userName=JwtUtils.parseRefreshTokenClaims(refreshToken).map(Claims::getSubject);
if(userName.isPresent()){
AuthUserauthUser=sysUserService.loadUserByUsername(userName.get());
if(Objects.nonNull(authUser)){
LoginVOloginVO=newLoginVO();
loginVO.setUserName(authUser.getUsername());
loginVO.setAccessToken(JwtUtils.createAccessToken(authUser));
loginVO.setRefreshToken(JwtUtils.createRefreshToken(authUser));
returnloginVO;
}
thrownewInternalAuthenticationServiceException("用户不存在");
}
thrownewRuntimeException("认证失败");
}
}

Token过滤

publicclassJwtAuthenticationTokenFilterextendsOncePerRequestFilter{
@Override
protectedvoiddoFilterInternal(HttpServletRequestrequest,HttpServletResponseresponse,FilterChainchain)throwsServletException,IOException{
//checkToken
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());
UsernamePasswordAuthenticationTokenusernamePasswordAuthenticationToken=
newUsernamePasswordAuthenticationToken(claimsOptional.get().getSubject(),null,authorities);
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}else{
SecurityContextHolder.clearContext();
}
}
chain.doFilter(request,response);
}

publicstatic<T>List<T>castList(Objectobj,Class<T>clazz){
List<T>result=newArrayList<T>();
if(objinstanceofList<?>){
for(Objecto:(List<?>)obj){
result.add(clazz.cast(o));
}
returnresult;
}
returnnull;
}

privateOptional<Claims>validateToken(HttpServletRequestreq){
StringjwtToken=req.getHeader("token");
try{
returnJwtUtils.parseAccessTokenClaims(jwtToken);
}catch(ExpiredJwtException|SignatureException|MalformedJwtException|UnsupportedJwtException|IllegalArgumentExceptione){
//输出日志
returnOptional.empty();
}
}

privatebooleancheckJWTToken(HttpServletRequestrequest){
StringauthenticationHeader=request.getHeader("token");
returnauthenticationHeader!=null;
}
}

授权处理

全局授权的配置已经在核心配置中开启,核心思路是通过SecurityContextHolder获取当前用户权限,判断当前用户的权限是否包含该方法的权限,此部分设计后续如果存在性能问题,可以设计缓存来解决。

授权检查

publicclassSecuritySecurityCheckService{
publicbooleanhASPermission(Stringpermission){
returnhasAnyPermissions(permission);
}

publicbooleanhasAnyPermissions(String...permissions){
if(CollectionUtils.isEmpty(Arrays.asList(permissions))){
returnfalse;
}
Authenticationauthentication=SecurityContextHolder.getContext().getAuthentication();
if(authentication==null){
returnfalse;
}
Collection<?extendsGrantedAuthority>authorities=authentication.getAuthorities();
returnauthorities.stream().map(GrantedAuthority::getAuthority).filter(x->!x.contains("ROLE_"))
.anyMatch(x->PatternMatchUtils.simpleMatch(permissions,x));
}

publicbooleanhasRole(Stringrole){
returnhasAnyRoles(role);
}

publicbooleanhasAnyRoles(String...roles){
if(CollectionUtils.isEmpty(Arrays.asList(roles))){
returnfalse;
}
Authenticationauthentication=SecurityContextHolder.getContext().getAuthentication();
if(authentication==null){
returnfalse;
}
Collection<?extendsGrantedAuthority>authorities=authentication.getAuthorities();
returnauthorities.stream().map(GrantedAuthority::getAuthority).filter(x->x.contains("ROLE_"))
.anyMatch(x->PatternMatchUtils.simpleMatch(roles,x));
}
}

如何使用

@PreAuthorize("@ssc.hasPermission('sys:user:query')")
@PostMapping("/helloWord")
publicStringhellWord(){
return"helloword";
}

跨域问题处理

关于这部分跨域部分的配置还可以更加细化一点。

@Configuration
publicclassCorsConfigimplementsWebMvcConfigurer{
@Override
publicvoidaddCorsMappings(CorsRegistryregistry){
//设置允许跨域的路径
registry.addMapping("/**")
//设置允许跨域请求的域名
.allowedOriginPatterns("*")
//是否允许cookie
.allowCredentials(true)
//设置允许的请求方式
.allowedMethods("GET","POST","DELETE","PUT")
//设置允许的header属性
.allowedHeaders("*")
//跨域允许时间
.maxAge(3600);
}
}

vue-admin-template登录的简单探索感悟

这部分就是有些感悟(背景自身是没有接触过Vue相关的知识),具体的感悟就是不要畏惧一些自己不知道以及不会的东西,大胆的去尝试,因为自身的潜力是很大的。为什么要这么讲,通过自己折腾3个小时,自己完成整个登录过程,如果是前端可能会比较简单,针对我这种从没接触过的还是有些难度的,需要一些基础配置更改以及流程梳理,这里简单来让大家看下效果,后续我也会自己把剩下菜单动态加载以及一些简单表单交互来完成,做到简单的表单可以自己来实现。

Sping Security前后端分离两种实战方案

Sping Security前后端分离两种实战方案

到此这篇关于Sping Security前后端分离两种方案的文章就介绍到这了,更多相关Sping Security前后端分离内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新开发

开发排行榜