3.Spring Security实现JWT token验证

news2024/11/18 18:21:20

目录


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 {
    /**
     * 自定义登录成功处理器
     */
    @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/455251.html

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

相关文章

图数据库

1 前言 图数据结构&#xff0c;能够很自然地表征现实世界。比如用户、门店、骑手这些实体可以用图中的点来表示&#xff0c;用户到门店的消费行为、骑手给用户的送餐行为可以用图中的边来表示。使用图的方式对场景建模&#xff0c;便于描述复杂关系。在美团&#xff0c;也有比较…

超全实战操作详解合集——阿里云ECS服务器(CentOS 7.8 64位)安装rpm格式jdk、tomcat8、mysql8三件套

一、下载rpm格式的jdk 网上自行下载下载jdk&#xff0c;使用Xftp连接Linux主机&#xff0c;将jdk文件放入任意目录下&#xff08;为方便后续操作&#xff0c;重命名为1.rpm&#xff09; 二、安装jdk 进入存放jdk文件的目录&#xff0c;使用命令yum -y install 1.rpm 进行安装…

前端工具 Prettier 详细使用流程(兼容ESLint)

一、简介 中文文档&#xff0c;英文官网。 Prettier 是一个开箱即用的代码格式化程序。用来批量处理旧代码的统一&#xff0c;涉及引号、分号、换行、缩进等。支持目前大部分语言处理&#xff0c;包括 JavaScript、Flow、TypeScript、CSS、SCSS、Less、JSX、Vue、GraphQL、JSO…

【计算机视觉】华为天才少年谢凌曦:关于视觉识别领域发展的个人观点!

文章目录 一、前言二、CV的三大基本困难和对应研究方向三、以下简要分析各个研究方向3.1 方向1a&#xff1a;神经网络架构设计3.2 方向1b&#xff1a;视觉预训练3.3 方向2&#xff1a;模型微调和终身学习3.4 方向3&#xff1a;无限细粒度视觉识别任务 四、在上述方向之外五、结…

“踏浪”自动驾驶量产潮,商业化加速的知行科技奔赴IPO

今春过半&#xff0c;自动驾驶产业链扎堆上市的情况在延续&#xff0c;一位新的重量级选手加入了这场热潮。 4月4日&#xff0c;自动驾驶领域领先的解决方案提供商知行汽车科技(苏州)股份有限公司&#xff08;即“知行科技”&#xff09;&#xff0c;向港交所递交招股书&#…

全网多种方法解决error: failed to push some refs to ‘xxx‘

文章目录 1. 复现错误2. 分析错误3. 解决错误4. 解决该错误的其他方法 1. 复现错误 今天使用git status查看文件状态&#xff0c;发现有一个文件未提交&#xff0c;如下代码所示&#xff1a; D:\project\test>git status On branch master Your branch is up to date with …

【剑指offer】学习计划day1

目录 一. 前言 二. 用两个栈实现队列 a.题目 b.题解分析 c.AC代码 二. 包含min函数的栈 a.题目 b.题解分析 c.AC代码 一. 前言 本系列是针对Leetcode中剑指offer学习计划的记录与思路讲解。详情查看以下链接&#xff1a; 剑指offer-学习计划https://leetcode.cn/study-pla…

Java八大基本数据类型

Java八大基本数据类型 byteshortintlongfloatdoublebooleanchar byte byte数据类型是8位、有符号的&#xff0c;以二进制补码表示的整数 最小值是-128&#xff08;-2^7&#xff09;&#xff1b; 最大值是127&#xff08;2^7-1&#xff09;; 默认值是0&#xff1b; byte类型用在…

stm32读写内部Flash

stm32内部flash地址架构映射 因为我的stm32f407的内部flash是1M的所以块2不存在&#xff0c;但他的地址仍然存在&#xff0c;只是没有作用&#xff0c;这是stm32的整体框架。 主存储器 一般我们说 STM32 内部 FLASH 的时候&#xff0c;都是指这个主存储器区域&#xff0c;它…

duilib窗口拖动

直接使用如下就可以了&#xff0c;不用再使用继承。 LRESULT CXmlWnd::OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {POINT pt;RECT rcClient;RECT rcCaption;CControlUI * pControl NULL;rcCaption m_P…

C++练级之初级:第五篇

C练级之初级&#xff1a;第五篇 第五篇 C练级之初级&#xff1a;第五篇1.auto关键字2.for循环改进3.指针空值nullptr4.内联函数4.1内联函数的概念4.2内联函数的注意点 总结 1.auto关键字 &#x1f914;什么是auto(automatic的缩写&#xff0c;自动的意思)关键字&#xff1f; au…

OpenShift 4 - 在 CI/CD Pipeline 中创建 KubeVirt 容器虚拟机 - 方法1+2 (视频)

《OpenShift / RHEL / DevSecOps 汇总目录》 说明&#xff1a;本文已经在支持 OpenShift 4.12 的 OpenShift 环境中验证 文章目录 准备环境安装可实现 KubeVirt 操作的 Tekton 资源创建密钥对 在 CI/CD 流水线管道中创建 VM方法1&#xff1a;通过 Manifest 任务创建 VM方法2&am…

如何实现Spring AOP以及Spring AOP的实现原理

AOP:面向切面编程,它和OOP&#xff08;面向对象编程)类似。 AOP组成: 1、切面:定义AOP是针对那个统一的功能的&#xff0c;这个功能就叫做一个切面&#xff0c;比如用户登录功能或方法的统计日志&#xff0c;他们就各种是一个切面。切面是有切点加通知组成的。 2、连接点:所有可…

ClickHouse快速入门

目录 1 ClickHouse介绍1.1 ClickHouse 的特点1.1.1 列式存储1.1.2 DBMS 的功能1.1.3 多样化引擎1.1.4 高吞吐写入能力1.1.5 数据分区与线程级并行1.1.6 性能对比 2 数据类型2.1 整型2.2 浮点型2.3 布尔型2.4 Decimal 型2.5 字符串2.6 枚举类型2.7 时间类型2.8 数组 3 表引擎3.1…

SpringBoot tomcat核心参数

server.tomcat.threads.min-spare10server.tomcat.threads.max200server.tomcat.max-connections8192server.tomcat.accept-count100 第一个参数代表程序启动就会开启10个线程。 如果我改成20个&#xff0c;看看什么情况&#xff0c;可以看到初始化了20个线程 看第二个参数&am…

2022年中国云市场份额:阿里云腾讯云下降

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 4月23日&#xff0c;IDC发布《中国公有云服务市场(2022下半年)跟踪》&#xff0c;占据前四的分别为阿里云(40.6%)、华为云(11.0%)、腾讯云(11.0%)、中国电信(8.7%)。咱们说重点&#xff0c;如下图所…

DFMEA 在车用燃料电池空压机设计中的应用

摘要&#xff1a; DFMEA在空压机研发中的应用 氢气具有资源丰富、热值高和无污染等特点&#xff0c;因而是燃料电池汽车最理想的二次能源。空压机作为燃料电池汽车的关键总成&#xff0c;掌握其核心部件的设计和制造技术非常必要。应用传统的设计方法进行相关零部件如空气轴承…

D3.js(2) Data-Join

什么是Data-Join&#xff1f; 本质上是将数据与图元绑定 可以省去大量根据数据设置图元属性的代码量&#xff0c;对动态变化的数据提供统一接口 D3.js绑定数据的三个状态 Enter 数据数量>图元数量&#xff0c;d3.js会根据新增的数据生成相应的图元 给不存在数据绑定的图…

跨境电商服务简单说明

​ 前言 跨境电商服务是指通过互联网平台进行国际贸易的一种新型商业模式&#xff0c;涉及跨境物流、跨境支付、跨境电商平台等多个领域。随着全球化的不断推进和消费者需求的不断变化&#xff0c;跨境电商服务行业呈现出快速发展的趋势。 发展背景 跨境电商服务行业的发展背景…

基于ArcGIS Pro、R、INVEST等多技术融合下生态系统服务权衡与协同动态分析

生态系统服务是指生态系统所形成的用于维持人类赖以生存和发展的自然环境条件与效用&#xff0c;是人类直接或间接从生态系统中得到的各种惠益。联合国千年生态系统评估&#xff08;Millennium ecosystem assessment&#xff0c;MA&#xff09;提出生态系统服务包括供给、调节、…