Spring Security实现JWT token验证

news2024/11/19 14:50:53

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 {
    /**
     * 自定义登录成功处理器
     */
    @Autowired
    private UserLoginSuccessHandler userLoginSuccessHandler;
    
    /**
     * 自定义登录失败处理器
     */
    @Autowired
    private UserLoginFailureHandler userLoginFailureHandler;
    /**
     * 自定义注销成功处理器
     */
    @Autowired
    private UserLogoutSuccessHandler userLogoutSuccessHandler;
    
    /**
     * 自定义暂无权限处理器
     */
    @Autowired
    private UserAuthAccessDeniedHandler userAuthAccessDeniedHandler;
    
    /**
     * 自定义未登录的处理器
     */
    @Autowired
    private UserAuthenticationEntryPointHandler userAuthenticationEntryPointHandler;
    /**
     * 自定义登录逻辑验证器
     */
    @Autowired
    private UserAuthenticationProvider userAuthenticationProvider;

    /**
     * 加密方式
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder(){
        return new BCryptPasswordEncoder();
    }

    /**
     * 注入自定义PermissionEvaluator
     */
    @Bean
    public DefaultWebSecurityExpressionHandler userSecurityExpressionHandler(){
        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
        handler.setPermissionEvaluator(new UserPermissionEvaluator());
        return handler;
    }

    /**
     * 配置登录验证逻辑
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth){
        //启用自定义的登陆验证逻辑
        auth.authenticationProvider(userAuthenticationProvider);
    }

    /**
     * 配置security的控制逻辑
     * @Param  http 请求
     */
    @Override
    protected 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不需要session
        http.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:
  # 密匙KEY
  secret: secret
  # Header KEY
  tokenHeader: infinite-auth
  # Token前缀字符
  tokenPrefix: authorization-
  # 过期时间 单位秒 1天后过期=86400 7天后过期=604800
  expiration: 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 {
    /**
     * 登录成功返回结果
     */
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication){
        // 组装JWT
        SelfUserEntity 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 {
    /**
     * 登录失败返回结果
     */
    @Override
    public 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
     */
    @Override
    public 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 {
    /**
     * 暂无权限返回结果
     */
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException exception){
        ResultUtil.responseJson(response,ResultUtil.resultCode(403,"未授权"));
    }
}

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

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

5.自定登录验证(AuthenticationProvider)


@Component
public class UserAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    private UsersService usersService;
    @Autowired
    private UsermetaService usermetaService;

    @Override
    public 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);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return true;
    }
}

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

@Component
public class UserPermissionEvaluator implements PermissionEvaluator {
    @Autowired
    private UsermetaService usermetaService;

    /**
     * hasPermission鉴权方法
     * 这里仅仅判断PreAuthorize注解中的权限表达式
     * 实际中可以根据业务需求设计数据库通过targetUrl和permission做更复杂鉴权
     * 当然targetUrl不一定是URL可以是数据Id还可以是管理员标识等,这里根据需求自行设计
     * @Param  authentication  用户身份(在使用hasPermission表达式时Authentication参数默认会自动带上)
     * @Param  targetUrl  请求路径
     * @Param  permission 请求路径权限
     * @Return boolean 是否通过
     */
    @Override
    public 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;
    }

    @Override
    public 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);
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        // 获取请求头中JWT的Token
        String tokenHeader = request.getHeader(JWTConfig.tokenHeader);

        if (null != tokenHeader && tokenHeader.startsWith(JWTConfig.tokenPrefix)) {
            try {
                // 截取JWT前缀
                String token = tokenHeader.replace(JWTConfig.tokenPrefix, "");
                // 解析JWT
                Claims 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 {

    @Autowired
    private UsersService usersService;

    /**
     * 查询用户信息
     *
     * @Param username  用户名
     * @Return UserDetails SpringSecurity用户信息
     */
    @Override
    public 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.coloradmin.cn/o/454944.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Dubbo核心 详解三】Dubbo服务接口的详解

✅创作者:陈书予 🎉个人主页:陈书予的个人主页 🍁陈书予的个人社区,欢迎你的加入: 陈书予的社区 🌟专栏地址: Dubbo专栏 文章目录 引言一、简介1. 介绍 Dubbo 服务接口的基本概念和特点1.1 Dubbo 服务接口的基础概念1.2 Dubbo 服务接口的特点2. 介绍 Dubbo 服务接口的…

机器学习——SVM的易错题型

问&#xff1a;支持向量机仅可以用于处理二分类任务 答&#xff1a;错误。支持向量机可以用于处理多分类任务&#xff0c;通过使用一对多或一对一的方法&#xff0c;将多个类别分别与其他类别做二分类。也可以使用多类支持向量机算法&#xff0c;直接将多个类别一起纳入训练和…

路侧激光雷达目标检测系统-篇1

说明&#xff1a;又到了毕业的季节&#xff0c;拿出来我之前做的小雷达识别项目&#xff0c;给学弟学妹们做毕设一点参考。这个主要是根据雷达采集的数据包进行聚类识别&#xff0c;看那些是汽车&#xff0c;更改数据的特征之后可以识别特定目标&#xff0c;比如路上新人等。  …

SpringCloud --- Nacos注册中心

一、认识和安装Nacos Nacos是阿里巴巴的产品&#xff0c;现在是SpringCloud中的一个组件。相比Eureka功能更加丰富&#xff0c;在国内受欢迎程度较高。 二、服务注册到nacos Nacos是SpringCloudAlibaba的组件&#xff0c;而SpringCloudAlibaba也遵循SpringCloud中定义的服务注…

Stable Diffusion公司发布首个大语言模型StableLM,已开源公测!

文 | 智商掉了一地 20号凌晨&#xff0c;Stability AI 发布了一个新的开源语言模型—— StableLM&#xff0c;该公司曾开发了 Stable Diffusion 图像生成工具。这则新闻意味着它不再局限于图像与视频生成领域&#xff0c;将正式加入文本生成 AI 赛道。 StableLM 模型可以生成文…

企业号运营全攻略,让你的品牌更具竞争力

实体企业抖音矩阵运营主要包含以下五个方面&#xff1a;多平台帐号绑定、短视频制作、短视频发布、私信评论维护以及提供数据分析报表。   一、多平台帐号绑定   多平台帐号绑定是实体企业进行抖音矩阵运营的第一步。通过将企业的各种社交账号与抖音账号进行绑定&#xff0…

CoreMark 测试指南

1、coremark 简介 coremark 是由EEMBC提出的一个评价CPU性能指标的跑分软件。其主要目标是测试处理器核心性能。CoreMark程序使用C语言写成&#xff0c;包含如下四类运算法则&#xff1a;数学矩阵操作&#xff08;普通矩阵运算&#xff09;、列举&#xff08;寻找并排序&#…

[2019.01.25]Android NDK Crash错误定位

Android NDK开发Crash错误定位: D:\Users\Android\Sdk ndk-stack.exe: D:\Users\Android\Sdk\ndk-bundle\prebuilt\windows-x86_64\bin aarch64-linux-android-addr2line.exe: D:\Users\Android\Sdk\ndk-bundle\toolchains\ aarch64-linux-android-4.9\prebuilt\windows-x86_64…

六、Golang的并发

Go语言的并发指的是能让某个函数独立于其他函数运行的能力。当一个函数创建为goroutine时&#xff0c;Go会将其视为一个独立的工作单元。这个单元会被调度到可用的逻辑处理器上执行。 Go语言运行时的调度器是一个复杂的软件&#xff0c;能管理被创建的所有goroutine并为其分配执…

对考研考公的过分执念,正在悄悄束缚你的职场选择!

随着近年来就业形势的严峻&#xff0c;越来越多的同学在找工作时碰壁&#xff0c;尤其是对于大部分应届生&#xff0c;这种现象尤为明显。 每年数百万的大学生进入到社会&#xff0c;却发现能选择的机会并不多。高等教育规模不断扩大的背景下&#xff0c;职场晋升的门槛越来越…

Hudi最流行数据湖框架介绍

目录 1. 第一章Hudi 框架概述1.1 数据湖Data Lake1.1.1 仓库和湖泊1.1.2 什么是数据湖1.1.3 数据湖的优点1.1.4 Data Lake vs Data warehouse1.1.5 数据湖框架1.1.5.1 Delta Lake1.1.5.2 Apache Iceberg1.1.5.3 Apache Hudi 1.1.6 湖仓一体&#xff08;Data Lakehouse&#xff…

【1】从零开始学习目标检测:YOLO算法详解

从零开始学习目标检测&#xff1a;YOLO算法详解 文章目录 从零开始学习目标检测&#xff1a;YOLO算法详解1. &#x1f31f;什么是目标检测?2.&#x1f31f;传统的目标检测与基于深度学习的目标检测3.&#x1f31f;目标检测算法的工作流程4.&#x1f31f;目标检测可以干什么&am…

拿到新的服务器必做的五件事(详细流程,开发必看)

目录 1. 配置免密登录 基本用法 远程登录服务器&#xff1a; 第一次登录时会提示&#xff1a; 配置文件 创建文件 然后在文件中输入&#xff1a; 密钥登录 创建密钥&#xff1a; 2.部署nginx 一、前提条件 二、安装 Nginx 3.配置python虚拟环境 1.安装虚拟环境 …

自习室管理系统的设计与实现(论文+源码)_kaic

摘要 近年来&#xff0c;随着高校规模的逐步扩大&#xff0c;学生对高校自习室座位的需求也在不断增加。然而&#xff0c;一些高校仍然采用人工管理学院自习室座位&#xff0c;这大大降低了管理效率。显然&#xff0c;开发一个成本低、占用资源少、能提高高校自习室座位管理效率…

WindowsHash简介及windows认证

Windows系统使用两种方法对用户的密码进行哈希处理&#xff0c;他们分别是LAN Manager(LM)哈希和NT LAN Manager(NTML)哈希。 现在已经有了更新的NTLMv2以及Kerberos验证体系。 Windows的系统密码hash默认情况下一般由两个部分组成&#xff1a;第一部分是LM-hash&#xff0c;…

Nginx中间件漏洞复现

Nginx 解析漏洞 该漏洞与nginx、php版本无关&#xff0c;属于用户配置不当造成的解析漏洞。 漏洞原理&#xff1a; 该解析漏洞是PHP fastcgi 的漏洞&#xff0c;在PHP的配置文件 php.ini 中有一个关键的选项 cgi.fix_pathinfo 默认值为1&#xff0c;表示开启。同时在 php-fp…

ASO优化之如何回复Google Play评论

应用的平均评分会影响 Google Play 商店优化 和应用的 Google Play 排名。应用的评分越高&#xff0c;我们在搜索结果中的排名就越靠前。因此&#xff0c;当应用处于 4 星评级范围内时&#xff0c;它会被更多 Google Play 商店的访问者看到和发现。我们可以使用应用雷达中的评级…

Linux进程通信:有名管道

有名管道&#xff1a; 无名管道只能用于有亲缘关系的进程间通信。 因此提出有名管道&#xff08;也叫FIFO文件&#xff09;&#xff0c;以实现无亲缘关系进程间的通信。 不同于无名管道&#xff0c;有名管道FIFO文件的形式存在于文件系统&#xff0c;与一个路径名关联&#xff…

【复杂网络建模】——Python可视化重要节点识别(PageRank算法)

目录 一、复杂网络建模 二、建模的算法 三、使用PageRank算法进行网络重要节点识别 1、PageRank算法 2、基于PageRank算法的ER网络重要节点识别 3、基于PageRank算法的小世界网络重要节点识别 4、基于PageRank算法的无标度网络的重要节点识别 四、ER网络、小世界网络、…

春秋云境:CVE-2022-24663(远程代码执行漏洞exp)

目录 一、题目 二、构造exp执行php 三、蚁剑连接 一、题目 介绍&#xff1a; 远程代码执行漏洞&#xff0c;任何订阅者都可以利用该漏洞发送带有“短代码”参数设置为 PHP Everywhere 的请求&#xff0c;并在站点上执行任意 PHP 代码。P.S. 存在常见用户名低权限用户弱口令 …