写点什么

Sping Security 前后端分离两种方案

作者:EquatorCoco
  • 2023-07-10
    福建
  • 本文字数:13906 字

    阅读完需:约 46 分钟

前言


本篇文章是基于 Spring Security 实现前后端分离登录认证及权限控制的实战,主要包括以下四方面内容:


  1. Spring Seciruty 简单介绍;

  2. 通过 Spring Seciruty 实现的基于表单和 Token 认证的两种认证方式;

  3. 自定义实现 RBAC 的权限控制;

  4. 跨域问题处理;


Spring Seciruty 简单介绍


Spring Security 是基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。关于安全方面的两个核心功能是认证和授权,Spring Security 重要核心功能就是实现用户认证(Authentication)和用户授权(Authorization)。


认证(Authentication)


认证是用来验证某个用户能否访问该系统。用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证过程。认证获取:http://www.jnpfsoft.com/?from=infoq


授权(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 模型最简单奔版本的数据库设计,用户、角色、权限表;



基于表单认证


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



核心配置


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


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

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

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

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

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

  6. 自定义加密的 Bean;

  7. 自定义授权认证 Bean;


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


//Spring Security框架开启@EnableWebSecurity//授权全局配置@EnableGlobalMethodSecurity(prePostEnabled = true)@Configurationpublic 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 {}
@Servicepublic 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); }}
@Servicepublic 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)@Configurationpublic 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 创建


@Servicepublic 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";}
复制代码


跨域问题处理


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


@Configurationpublic 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 个小时,自己完成整个登录过程,如果是前端可能会比较简单,针对我这种从没接触过的还是有些难度的,需要一些基础配置更改以及流程梳理,这里简单来让大家看下效果,后续我也会自己把剩下菜单动态加载以及一些简单表单交互来完成,做到简单的表单可以自己来实现。




文章转载自:大魔王先生

原文链接:https://www.cnblogs.com/wtzbk/p/17260243.html


用户头像

EquatorCoco

关注

还未添加个人签名 2023-06-19 加入

还未添加个人简介

评论

发布
暂无评论
Sping Security前后端分离两种方案_前端_EquatorCoco_InfoQ写作社区