3.Spring Security实现JWT token验证

news/2024/4/26 16:49:03/文章来源:https://blog.csdn.net/qq_45305209/article/details/130335428

目录


1. Spring Security详细介绍

2. Spring Security详细使用

3. Spring Security实现JWT token验证

4. JWT(JSON Web Token,JSON令牌)

5. Spring Security安全注解




Spring Security实现JWT token验证

Spring Security是Spring提供的一个安全框架,提供认证和授权功能,最主要的是它提供了简单的使用方式,同时又有很高的灵活性,简单、灵活、强大
一般系统里关于角色方面通常有这么几张表:用户表、角色表、用户-角色表、菜单表、角色-菜单表、权限表、角色-权限表等

1.导入依赖

JWT认证的实现
(1)支持用户通过用户名和密码登录
(2)登录后通过http header返回token,每次请求,客户端需通过header将token带回,用于权限校验
(3)服务端负责token的定期刷新

<properties><jjwt.version>0.9.1</jjwt.version><spring-security-jwt.version>1.0.11.RELEASE</spring-security-jwt.version>
</properties><!-- Spring Security-->
<dependencies><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId></dependency><dependency><groupId>org.springframework.security</groupId><artifactId>spring-security-jwt</artifactId><version>${spring-security-jwt.version}</version></dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>${jjwt.version}</version></dependency>
</dependencies>

2.Spring Security配置类

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)  // 开启权限注解,默认是关闭的
public class SecurityConfig extends WebSecurityConfigurerAdapter {/*** 自定义登录成功处理器*/@Autowiredprivate UserLoginSuccessHandler userLoginSuccessHandler;/*** 自定义登录失败处理器*/@Autowiredprivate UserLoginFailureHandler userLoginFailureHandler;/*** 自定义注销成功处理器*/@Autowiredprivate UserLogoutSuccessHandler userLogoutSuccessHandler;/*** 自定义暂无权限处理器*/@Autowiredprivate UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;/*** 自定义未登录的处理器*/@Autowiredprivate UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;/*** 自定义登录逻辑验证器*/@Autowiredprivate UserAuthenticationProvider userAuthenticationProvider;/*** 加密方式*/@Beanpublic BCryptPasswordEncoder bCryptPasswordEncoder(){return new BCryptPasswordEncoder();}/*** 注入自定义PermissionEvaluator*/@Beanpublic DefaultWebSecurityExpressionHandler userSecurityExpressionHandler(){DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();handler.setPermissionEvaluator(new UserPermissionEvaluator());return handler;}/*** 配置登录验证逻辑*/@Overrideprotected void configure(AuthenticationManagerBuilder auth){//启用自定义的登陆验证逻辑auth.authenticationProvider(userAuthenticationProvider);}/*** 配置security的控制逻辑* @Param  http 请求*/@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests()// 不进行权限验证的请求或资源(从配置文件中读取).antMatchers(JWTConfig.antMatchers.split(",")).permitAll()// .antMatchers("/*").permitAll()// 其他的需要登陆后才能访问.anyRequest().authenticated().and()// 配置未登录自定义处理类.httpBasic().authenticationEntryPoint(userAuthenticationEntryPointHandler).and()// 配置登录地址.formLogin().loginProcessingUrl("/login/userLogin")// 配置登录成功自定义处理类.successHandler(userLoginSuccessHandler)// 配置登录失败自定义处理类.failureHandler(userLoginFailureHandler).and()// 配置登出地址.logout().logoutUrl("/login/userLogout")// 配置用户登出自定义处理类.logoutSuccessHandler(userLogoutSuccessHandler).and()// 配置没有权限自定义处理类.exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler).and()// 开启跨域.cors().and()// 取消跨站请求伪造防护.csrf().disable();// 基于Token不需要sessionhttp.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);// 禁用缓存http.headers().cacheControl();// 添加JWT过滤器http.addFilter(new JWTAuthenticationTokenFilter(authenticationManager()));}
}

3.编写JWTConfig和application.yml增加jwt相关配置

// JWT相关配置类
@Data
@Component
@ConfigurationProperties(prefix = "jwt")
public class JwtConfig {/*** 密钥KEY*/private String secret;/*** TokenKey*/private String tokenHeader;/*** Token前缀字符*/private String tokenPrefix;/*** 过期时间*/private Integer expiration;/*** 不需要认证的接口*/private String antMatchers;/*** jwt发行人*/private String issuer;/*** 主体*/private String subject;/*** 接受者*/private String audience;/*** 有效时间*/private String validTime;
}

application.yml

# JWT配置
jwt:# 密匙KEYsecret: secret# Header KEYtokenHeader: infinite-auth# Token前缀字符tokenPrefix: authorization-# 过期时间 单位秒 1天后过期=86400 7天后过期=604800expiration: 86400# 配置不需要认证的接口antMatchers: /generator/table/index# jwt发行人issuer: infinite# 主体subject: to-infinite# 接受者audience: infinite-user# 有效时间,分钟validTime: 30

4.编写过滤器处理类

(1)登录成功处理类(AuthenticationSuccessHandler)

@Component
public class UserLoginSuccessHandler implements AuthenticationSuccessHandler {/*** 登录成功返回结果*/@Overridepublic void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){// 组装JWTSelfUserEntity selfUserEntity =  (SelfUserEntity) authentication.getPrincipal();String token = JWTTokenUtil.createAccessToken(selfUserEntity);token = JWTConfig.tokenPrefix + token;// 封装返回参数Map<String,Object> resultData = new HashMap<>();resultData.put("code","200");resultData.put("msg", "登录成功");resultData.put("token",token);ResultUtil.responseJson(response,resultData);}
}

(2)登录失败处理类(AuthenticationFailureHandler)

@Component
public class UserLoginFailureHandler implements AuthenticationFailureHandler {/*** 登录失败返回结果*/@Overridepublic void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){// 这些对于操作的处理类可以根据不同异常进行不同处理if (exception instanceof UsernameNotFoundException){System.out.println("【登录失败】"+exception.getMessage());ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用户名不存在"));}if (exception instanceof LockedException){System.out.println("【登录失败】"+exception.getMessage());ResultUtil.responseJson(response,ResultUtil.resultCode(500,"用户被冻结"));}if (exception instanceof BadCredentialsException){System.out.println("【登录失败】"+exception.getMessage());ResultUtil.responseJson(response,ResultUtil.resultCode(500,"密码错误"));}ResultUtil.responseJson(response,ResultUtil.resultCode(500,"登录失败"));}
}

(3)登出成功处理类(LogoutSuccessHandler)

@Component
public class UserLogoutSuccessHandler implements LogoutSuccessHandler {/*** 用户登出返回结果,前端清除掉Token*/@Overridepublic void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){Map<String,Object> resultData = new HashMap<>();resultData.put("code","200");resultData.put("msg", "登出成功");SecurityContextHolder.clearContext();ResultUtil.responseJson(response,ResultUtil.resultSuccess(resultData));}
}

(4)暂无权限处理类(AccessDeniedHandler)

@Component
public class UserAuthAccessDeniedHandler implements AccessDeniedHandler {/*** 暂无权限返回结果*/@Overridepublic void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception){ResultUtil.responseJson(response,ResultUtil.resultCode(403,"未授权"));}
}

(5)用户未登录处理类(AuthenticationEntryPoint)

@Component
public class UserAuthenticationEntryPointHandler implements AuthenticationEntryPoint {/*** 用户未登录返回结果*/@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception){ResultUtil.responseJson(response,ResultUtil.resultCode(401,"未登录"));}
}

5.自定登录验证(AuthenticationProvider)


@Component
public class UserAuthenticationProvider implements AuthenticationProvider {@Autowiredprivate UsersService usersService;@Autowiredprivate UsermetaService usermetaService;@Overridepublic Authentication authenticate(Authentication authentication) throws AuthenticationException {// 获取表单输入中返回的用户名String userName = (String) authentication.getPrincipal();// 获取表单中输入的密码String password = (String) authentication.getCredentials();// 查询用户是否存在SelfUserEntity userInfo = usersService.getUserInfo(userName);if (userInfo.getUsername() == null || userInfo.getUsername() == "") {throw new UsernameNotFoundException("用户名不存在");}// 判断密码是否正确,这里我们的密码使用BCryptPasswordEncoder进行加密的if (!new BCryptPasswordEncoder().matches(password, userInfo.getPassword())) {throw new BadCredentialsException("密码不正确");}// 还可以加一些其他信息的判断,比如用户账号已停用等判断if (userInfo.getStatus().equals("1")) {throw new LockedException("该用户已被冻结");}// 角色集合Set<GrantedAuthority> authorities = new HashSet<>();EntityWrapper<Usermeta> roleWrapper = new EntityWrapper<>();roleWrapper.eq("user_id",userInfo.getUserId());roleWrapper.eq("meta_key","wp_user_level");// 查询用户角色List<Usermeta> sysRoleEntityList = usermetaService.selectList(roleWrapper);for (Usermeta sysRoleEntity: sysRoleEntityList){authorities.add(new SimpleGrantedAuthority("ROLE_" + sysRoleEntity.getMetaValue()));}userInfo.setAuthorities(authorities);// 进行登录return new UsernamePasswordAuthenticationToken(userInfo, password, authorities);}@Overridepublic boolean supports(Class<?> authentication) {return true;}
}

6.自定义权限注解验证(PermissionEvaluator)

@Component
public class UserPermissionEvaluator implements PermissionEvaluator {@Autowiredprivate UsermetaService usermetaService;/*** hasPermission鉴权方法* 这里仅仅判断PreAuthorize注解中的权限表达式* 实际中可以根据业务需求设计数据库通过targetUrl和permission做更复杂鉴权* 当然targetUrl不一定是URL可以是数据Id还可以是管理员标识等,这里根据需求自行设计* @Param  authentication  用户身份(在使用hasPermission表达式时Authentication参数默认会自动带上)* @Param  targetUrl  请求路径* @Param  permission 请求路径权限* @Return boolean 是否通过*/@Overridepublic boolean hasPermission(Authentication authentication, Object targetUrl, Object permission) {// 获取用户信息Usermeta selfUserEntity =(Usermeta) authentication.getPrincipal();// 查询用户权限(这里可以将权限放入缓存中提升效率)Set<String> permissions = new HashSet<>();EntityWrapper<Usermeta> roleWrapper = new EntityWrapper<>();roleWrapper.eq("user_id",selfUserEntity.getUserId());roleWrapper.eq("meta_key","wp_user_level");List<Usermeta> sysMenuEntityList = usermetaService.selectList(roleWrapper);for (Usermeta sysMenuEntity:sysMenuEntityList) {permissions.add(sysMenuEntity.getMetaValue());}// 权限对比if (permissions.contains(permission.toString())){return true;}return true;}@Overridepublic boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {return false;}
}

7.用户实体类(UserDetails)

Spring Security用户的实体,必须要实现UserDetails接口。

public class SelfUserEntity implements Serializable, UserDetails {private static final long serialVersionUID = 1L;/*** 用户ID*/private Long userId;/*** 用户名*/private String username;/*** 密码*/private String password;/*** 状态*/private String status;/*** 显示名称*/private String displayName;/*** 用户参数*/private Map<String, String> userParamMap;/*** 用户角色*/private Collection<GrantedAuthority> authorities;/*** 账户是否过期*/private boolean isAccountNonExpired = false;/*** 账户是否被锁定*/private boolean isAccountNonLocked = false;/*** 证书是否过期*/private boolean isCredentialsNonExpired = false;/*** 账户是否有效*/private boolean isEnabled = true;// 省略getter/setter
}

8.JWT接口请求拦截器(BasicAuthenticationFilter)

JWT接口请求校验拦截器,请求接口时会进入这里验证Token是否合法和过期

public class JWTAuthenticationTokenFilter extends BasicAuthenticationFilter {public JWTAuthenticationTokenFilter(AuthenticationManager authenticationManager) {super(authenticationManager);}@Overrideprotected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {// 获取请求头中JWT的TokenString tokenHeader = request.getHeader(JWTConfig.tokenHeader);if (null != tokenHeader && tokenHeader.startsWith(JWTConfig.tokenPrefix)) {try {// 截取JWT前缀String token = tokenHeader.replace(JWTConfig.tokenPrefix, "");// 解析JWTClaims claims = Jwts.parser().setSigningKey(JWTConfig.secret).parseClaimsJws(token).getBody();// 获取用户名String username = claims.getSubject();String userId = claims.getId();if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(userId)) {// 获取角色List<GrantedAuthority> authorities = new ArrayList<>();String authority = claims.get("authorities").toString();if (!StringUtils.isEmpty(authority)) {List<Map<String, String>> authorityMap = JSONObject.parseObject(authority, List.class);for (Map<String, String> role : authorityMap) {if (!StringUtils.isEmpty(role)) {authorities.add(new SimpleGrantedAuthority(role.get("authority")));}}}//组装参数SelfUserEntity selfUserEntity = new SelfUserEntity();selfUserEntity.setUsername(claims.getSubject());selfUserEntity.setUserId(Long.parseLong(claims.getId()));selfUserEntity.setAuthorities(authorities);UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(selfUserEntity, userId, authorities);SecurityContextHolder.getContext().setAuthentication(authentication);}} catch (ExpiredJwtException e) {System.out.println("Token过期");} catch (Exception e) {System.out.println("Token无效");}}filterChain.doFilter(request, response);}
}

9.Spring Security用户的业务实现

@Component
public class SelfUserDetailsService implements UserDetailsService {@Autowiredprivate UsersService usersService;/*** 查询用户信息** @Param username  用户名* @Return UserDetails SpringSecurity用户信息*/@Overridepublic SelfUserEntity loadUserByUsername(String username) throws UsernameNotFoundException {EntityWrapper<Users> wrapper = new EntityWrapper<>();//邮箱正则表达式String expr = "^([a-zA-Z0-9_\\-\\.]+)@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.)|(([a-zA-Z0-9\\-]+\\.)+))([a-zA-Z]{2,4}|[0-9]{1,3})$";//是否为邮箱if (username.matches(expr)) {wrapper.eq("user_email", username);} else {wrapper.eq("user_login", username);}// 查询用户信息Users sysUserEntity = usersService.selectOne(wrapper);if (sysUserEntity != null) {// 组装参数SelfUserEntity selfUserEntity = new SelfUserEntity();BeanUtils.copyProperties(sysUserEntity, selfUserEntity);return selfUserEntity;}return null;}
}

10.控制层Controller

@Secured
当@EnableGlobalMethodSecurity(securedEnabled=true)的时候,@Secured可以使用。

@PostMapping("/helloUser")
@Secured({"ROLE_normal","ROLE_admin"})
public Map<String, Object> initDashboard() {Map<String, Object> result = new HashMap<>();result.put(ResponseDict.RESPONSE_TITLE_KEY, "仪表盘初始化");result.put(ResponseDict.RESPONSE_DATA_KEY, dashboardService.initDashboard());return ResultUtil.resultSuccess(result);
}

说明:拥有normal或者admin角色的用户都可以方法helloUser()方法。另外需要注意的是这里匹配的字符串需要添加前缀“ROLE_“

@PreAuthorize
Spring的 @PreAuthorize/@PostAuthorize 注解更适合方法级的安全,也支持Spring 表达式语言,提供了基于表达式的访问控制。

当@EnableGlobalMethodSecurity(prePostEnabled=true)的时候,@PreAuthorize可以使用:

@PostMapping("/initDashboard")
@PreAuthorize("hasRole('100')")
public Map<String, Object> initDashboard() {Map<String, Object> result = new HashMap<>();result.put(ResponseDict.RESPONSE_TITLE_KEY, "仪表盘初始化");result.put(ResponseDict.RESPONSE_DATA_KEY, dashboardService.initDashboard());return ResultUtil.resultSuccess(result);
}

@PostAuthorize
@PostAuthorize 注解使用并不多,在方法执行后再进行权限验证,适合验证带有返回值的权限,Spring EL 提供 返回对象能够在表达式语言中获取返回的对象returnObject。

当@EnableGlobalMethodSecurity(prePostEnabled=true)的时候,@PostAuthorize可以使用:

@GetMapping("/getUserInfo")
@PostAuthorize(" returnObject!=null &&  returnObject.username == authentication.name")
public User getUserInfo() {Object pricipal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();User user;if("anonymousUser".equals(pricipal)) {user = null;} else {user = (User) pricipal;}return user;
}

11.登录测试

(1)拿到正确token,正常登录
(2)请求中如果不携带token的话,请求其它接口就会显示没有登录的提示
(3)登录账户无权限


GrantedAuthority接口

在UserDeitails接口里面有一个getAuthorities()方法。这个方法将返回此用户的所拥有的权限。这个集合将用于用户的访问控制,也就是Authorization
①权限:就是一个字符串。一般不会重复
②权限检查:就是查看用户权限列表中是否含有匹配的字符串

public interface GrantedAuthority extends Serializable {// AccessDecisionManager访问控制决策,返回一个字符串String getAuthority();
}

“角色”如何表示?与Shiro有何不同?

在Spring Security中,角色和权限共用GrantedAuthority接口,唯一的不同角色就是多了个前缀“ROLE_”,而且它没有Shiro的那种从属关系,即一个角色包含哪些权限等等。在Spring Security看来角色和权限时一样的,它认证的时候,把所有权限(角色、权限)都取出来,而不是分开验证。所以,在Security提供的UserDetailsService默认实现JdbcDaoImpl中,角色和权限都存储在auhtorities表中。而不是像Shiro那样,角色有个roles表,权限有个permissions表。以及相关的管理表等

实现类:JaasGrantedAuthority、LdapAuthority、SimpleGrantedAuthority、SwitchUserGrantedAuthority
JaasGrantedAuthority GrantedAuthority除了分配的角色之外,还拥有主体,使用AuthorityGranter作为授予此权限的理由
SwitchUserGrantedAuthority 存储原始用户的身份验证对象,以便在以后从用户交换机“退出”时使用。

GrantedAuthority接口的默认实现SimpleGrantedAuthority

注意:在构建SimpleGrantedAuthority对象的时候,它没有添加任何前缀。所以表示“角色”的权限需要带有“ROLE_”前缀。Spring Security不管是角色、还是权限。它只比对字符串。所以角色需要加前缀。
角色信息存储的时候可以没有“ROLE_”前缀,包装成GrantedAuthority对象的时候必须要有

/**
* GrantedAuthority的基本具体实现。存储授予的权限的String(Authentication)
*/
public final class SimpleGrantedAuthority implements GrantedAuthority {private final String role;
}

权限检查/访问控制方式

权限检查有两种方式:
①在配置类中,指定粗粒度的访问控制
②使用注解细粒度的控制访问

(1)粗粒度访问控制

所有URL以"/admin"开头的用户必须拥有角色"ADMIN"才能访问。实际上操作的时候hasRole表达式,会判断参数是否包含"ROLE_"前缀,如果没有则加上去,然后再去校验。有这个前缀则直接校验。

protected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/admin/**").access("hasRole('ADMIN')").antMatchers("/user/**").access("hasRole('USER')").anyRequest().authenticated();
}

(2)细粒度的访问控制

注:需要使用注解@EnableGlobalMethodSecurity(prePostEnabled=true)开启

@PreAuthoritze("hasAuthority('readArtical')")
public List<Artical> getAll() {//...
}

@PreAuthoritze注解,会从SecurityContext中取出Authencation对象,然后再取出Collection、 authorites集合。然后比对当前用户是否有权限"readArtical"。实际上就是比对集合中是否有那个GrantedAuthority的getAuthority()方法返回的字符串与"radArtical"匹配。


开启权限注解

在任何@Configuration实例上使用@EnableGlobalMethodSecurity注解就能达到此目的。同时这个注解提供prePostEnabled、securedEnabled和jsr250Enabled三种不同的机制来实现同一种功能

(1)prePostEnabled

prePostEnabled = true 会解锁@PreAuthorize和@PostAuthorize两个注解。从名字就可以看出@PreAuthorize注解会在方法执行前进行验证,而@PostAuthorize注解会在方法执行后进行验证。

public interface UserService {List<User> findAllUsers();@PostAuthorize ("returnObject.type == authentication.name")User findById(int id);//  @PreAuthorize("hasRole('ADMIN')") 必须拥有ROLE_ADMIN角色。@PreAuthorize("hasRole('ROLE_ADMIN ')")void updateUser(User user);@PreAuthorize("hasRole('ADMIN') AND hasRole('DBA')")void deleteUser(int id);// @PreAuthorize("principal.username.startsWith('Felordcn')") 用户名开头为 Felordcn 的用户才能访问。// @PreAuthorize("#id.equals(principal.username)") 入参 id 必须同当前的用户名相同。// @PreAuthorize("#id < 10") 限制只能查询 id 小于 10 的用户}

常见内置表达式
https://docs.spring.io/spring-security/site/docs/4.0.1.RELEASE/reference/htmlsingle/#el-common-built-in
在这里插入图片描述

@PostAuthorize:
该注解使用不多,在方法执行后再进行权限验证。 适合验证带有返回值的权限。Spring EL 提供 返回对象能够在表达式语言中获取返回的对象returnObject。区别在于先执行方法。而后进行表达式判断。如果方法没有返回值实际上等于开放权限控制;如果有返回值实际的结果是用户操作成功但是得不到响应。允许方法调用,但是如果表达式计算结果为false,将抛出一个安全性异常。

@PreFilter:
对集合类型的参数执行过滤,移除结果为false的元素。基于方法入参相关的表达式,对入参进行过滤。分页慎用!该过程发生在接口接收参数之前。入参必须为 java.util.Collection且支持remove(Object)的参数。如果有多个集合需要通过 filterTarget=<参数名> 来指定过滤的集合。内置保留名称 filterObject 作为集合元素的操作名来进行评估过滤。

// 指定过滤的参数,过滤偶数
@PreFilter(filterTarget="ids", value="filterObject%2==0")
public void delete(List<Integer> ids, List<String> username)

@PostFilter:
和@PreFilter不同的是, 基于返回值相关的表达式,对返回值进行过滤。分页慎用!该过程发生接口进行数据返回之前


(2)Secured

@Secured注解是用来定义业务方法的安全配置。在需要安全[角色/权限等]的方法上指定 @Secured,并且只有那些角色/权限的用户才可以调用该方法。

@Secured缺点(限制)就是不支持Spring EL表达式。不够灵活。并且指定的角色必须以ROLE_开头,不可省略。该注解功能要简单的多,默认情况下只能基于角色(默认需要带前缀 ROLE_)集合来进行访问控制决策。该注解的机制是只要其声明的角色集合(value)中包含当前用户持有的任一角色就可以访问。也就是 用户的角色集合和 @Secured 注解的角色集合要存在非空的交集。 不支持使用 SpEL 表达式进行决策。

    @Secured({"ROLE_user"})void updateUser(User user);@Secured({"ROLE_admin", "ROLE_user1"})void updateUser();

(3)jsr250E

启用JSR-250安全控制注解,这属于JavaEE的安全规范(现为jakarta项目)。一共有五个安全注解。如果在@EnableGlobalMethodSecurity设置jsr250Enabled为true ,就开启了JavaEE 安全注解中的以下三个:

1.@DenyAll: 拒绝所有访问
2.@RolesAllowed({“USER”, “ADMIN”}): 该方法只要具有"USER", "ADMIN"任意一种权限就可以访问。这里可以省略前缀ROLE_,实际的权限可能是ROLE_ADMIN
3.@PermitAll: 允许所有访问

/** 配置没有权限自定义处理类** 注意:hasRole(),如果出现异常,会调用设置的accessDeniedHandler方法。*  .antMatchers("/index").hasRole("ADMIN")*  .anyRequest().authenticated();*/
httpSecurity.exceptionHandling().accessDeniedHandler(userAuthAccessDeniedHandler);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_103087.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Spring篇】DI相关内容

&#x1f353;系列专栏:Spring系列专栏 &#x1f349;个人主页:个人主页 目录 一、setter注入 1.环境准备 2.注入引用数据类型 3.注入简单数据类型 二、构造器注入 1.环境准备 2.构造器注入引用数据类型 3.构造器注入多个引用数据类型 4.构造器注入多个简单数据类型 …

SAS学习第4章:t检验

前话&#xff1a;分析试验数据的差异&#xff0c;一般都会假设样本值之间或者样本与标准值之间无差异&#xff0c;根据不同方法计算得出的t值、q值、F值等等&#xff0c;均表示两者之间的差异程度&#xff0c;值越大&#xff0c;两者差异越大&#xff0c;该假设越不成立&#x…

Vue3+Three.js+antvG2实战项目 智慧城市(三)

前言 在网上找了很久都没有找到使用Three.js开发智慧城市的免费文章或者免费视频,自己花了一点时间做了一个纯前端的智慧城市项目。 技术栈都是最新的:vue3vitetypeScriptThreeantv G2 源码分享 源码 模型,天空图盒子链接分享(不想下载源码可以只用下这个)提取码1234 20230424_…

TVM: An Automated End-to-End Optimizing Compiler for Deep Learning

https://www.usenix.org/conference/osdi18/presentation/chen 文章目录 TVM: An Automated End-to-End Optimizing Compiler for Deep Learning引言1. 简介2. 总览3. 优化计算图4. 生成张量运算4.1 张量表达式和调度空间4.3 嵌套并行与协作4.3 张量化4.4 显式内存延迟隐藏 5 .…

跌倒检测和识别4:C++实现跌倒检测(含源码,可实时跌倒检测)

跌倒检测和识别4&#xff1a;C实现跌倒检测(含源码&#xff0c;可实时跌倒检测) 目录 跌倒检测和识别4&#xff1a;C实现跌倒检测(含源码&#xff0c;可实时跌倒检测) 1. 前言 2. 跌倒检测模型&#xff08;YOLOv5&#xff09; &#xff08;1&#xff09;跌倒检测模型训练 …

2023云数据库技术沙龙MySQL x ClickHouse专场成功举办

4月22日&#xff0c;2023首届云数据库技术沙龙 MySQL x ClickHouse 专场&#xff0c;在杭州市海智中心成功举办。本次沙龙由玖章算术、菜根发展、良仓太炎共创联合主办。围绕“技术进化&#xff0c;让数据更智能”为主题&#xff0c;汇聚字节跳动、阿里云、玖章算术、华为云、腾…

【大数据之Hadoop】十九、MapReduce总结

MapTask工作机制 &#xff08;1&#xff09;Read阶段&#xff1a; job的提交流程&#xff1a;待读写的源数据由客户端进行切片划分&#xff0c;划分完成之后提交(切片信息、jar包、xml配置文件)给yarn&#xff0c;yarn开启MrAppMaster&#xff0c;MrAppMaster读取切片信息&…

10.java程序员必知必会类库之邮件

前言 邮件功能在当前互联网应用中已经是很成熟的功能&#xff0c;也是作为java程序员应该掌握的技能。常见使用场景有&#xff1a; 电商软件开电子发票&#xff0c;需要发到用户邮箱里面生产实时报警&#xff0c;需要发到邮箱里面银行软件申请的征信报告&#xff0c;电子账单…

《C++ Primer Plus》(第6版)第17章编程练习

《C Primer Plus》&#xff08;第6版&#xff09;第17章编程练习 《C Primer Plus》&#xff08;第6版&#xff09;第17章编程练习1. 计算输入流中第一个\$之前的字符数目2. 将键盘输入&#xff08;直到模拟的文件尾&#xff09;复制到通过命令行指定的文件中3. 将一个文件复制…

【难学易用c++ 之 继承】

目录&#xff1a; 前言一、继承的概念及定义&#xff08;一&#xff09;概念&#xff08;二&#xff09;继承定义继承关系和访问限定符继承基类成员访问方式的变化 二、基类和派生类对象赋值转换三、继承中的作用域四、派生类的默认成员函数五、继承与友元六、继承与静态成员七…

如何使用阿里云短信服务实现登录页面,手机验证码登录?

1&#xff1a;个人如何使用阿里云短信服务&#xff1f; 2022如何使用个人阿里云短信服务&#xff1f;_linxiMY的博客-CSDN博客添加完成之后&#xff0c;等待审核&#xff01;一般2个小时就会出来审核结果了&#xff0c;这里我因为注册申请时填写规则有误&#xff0c;足足审核了…

pytest 学习三(前置后置操作)

pytest测试框架_pytest框架-CSDN博客 一、常用的操作 一、setup/teardown 每个用例之前、之后执行 二、setup_class/teardown_class 在每个类之前、之后执行一次 二、pytest.fixture 设置前置后置操作范围 pytest.fixture(scope"",params,autouse,ids,name) 其中 sc…

JavaScript模块化开发

目录&#xff1a; 1 认识模块化开发 2 CommonJS和Node 3 require函数解析 4 AMD和CMD&#xff08;了解&#xff09; 5 ESModule用法详解 6 ESModule运行原理 模块化不是两个不同的js文件直接导入到某个页面中的&#xff0c;因为这两个文件只要有相同的变量或函数&#xf…

R基础函数概览(一)

rep 函数形式&#xff1a;rep(x, time , length , each ,) 参数说明&#xff1a; x&#xff1a;代表的是你要进行复制的对象&#xff0c;可以是一个向量或者是一个因子。 times&#xff1a;代表的是复制的次数&#xff0c;只能为正数。负数以及NA值都会为错误值。复制是指的…

云原生(docker+k8s+阿里云)-Docker

Gitee-Kubernetes学习 kubectl备忘清单 k8s官方文档-task [云原生-kubectl命令详解] ingress详解 ingress官方文档 云原生-语雀-架构师第一课 从Docker到Kubernetes进阶-社区 云计算学习路线-阿里云大学 如上图&#xff0c;服务器有公网ip和私网ip&#xff0c;公网ip是外部访问…

Ubuntu20.04使用多卡训练HyperNetwork模型和LoRA模型全流程及疑难问题解决方案

目录 一. LoRA模型多卡训练1.1 安装xformer等库1.2 设置路径1.3 多卡训练 二. LoRA模型多卡训练疑难报错解决方案多卡训练报错 软硬件配置&#xff1a; CPU: AMD 5800 8core 16Thread GPU: NVIDIA RTX 3090 *1 NVIDIA TITAN RTX *1 OS: Ubuntu20.04 一. LoRA模型多卡训练 1.1 …

一篇终结synchronized

一&#xff1a;基本原理 Java对象在内存中由两部分组成 &#xff1a; 1 是成员变量 2 是对象头&#xff0c;以32位虚拟机介绍&#xff1a;此时对象头是64位&#xff0c;即8字节 其中32个字节代表 mark word 另外32个字节代表klass word分别是什么意思呢&#xff1f; 1 klass …

写题总结1

先把自己写完的总结一下&#xff1a; 题目一&#xff1a; 猫儿园的告示牌上贴着 ab 大小的矩形广告纸。猫猫对广告不感兴趣&#xff0c;她想知道能否用 cd 的矩形白纸完全覆盖这个广告。猫猫可以对白纸进行平移、旋转&#xff0c;但不能折叠或撕开等。如果可以完全覆盖输出 YE…

滴水逆向3期笔记与作业——01汇编

防止OneNote丢失。 海哥牛逼。 01汇编笔记 01进制进制定义10-2进制转换八进制 02数据宽度/逻辑运算数据宽度与存储逻辑运算计算机做加法的本质作业 03通用寄存器_内存读写通用寄存器表通用寄存器图内存读写计算机操作系统位数意义 04内存地址_堆栈寻址公式PUSH指令POP指令作业 …

【IAR工程】STM8S基于ST标准库读取DHT11数据

【IAR工程】STM8S基于ST标准库读取DHT11数据 ✨申明&#xff1a;本文章仅发表在CSDN网站&#xff0c;任何其他网站&#xff0c;未注明来源&#xff0c;见此内容均为盗链和爬取&#xff0c;请多多尊重和支持原创!&#x1f341;对于文中所提供的相关资源链接将作不定期更换。&…