SpringSecurity的使用:为我们应用增加安全防护

构建安全稳定的应用:SpringSecurity实用指南 它涵盖了认证(Authentication)、授权(Authorization)、防止常见的安全攻击等多个方面,适用于各种类型的应用,包括 Web 应用、RESTful 服务、微服务等。- 生成认证对象:认证成功后,创建包含用户信息、角色和权限的 Authentication 对象,并存储在安全上下文中。- 加载用户角色和权限:除了基本的用户信息,还包括用户所拥有的角色和权限。- 认证请求处理:根据配置的认证方式,对用户提交的认证信息进行验证。 阅读详情

作为一名Java开发者,提起权限相关的功能,大家应该都知道Shiro和SpringSecurity两个安全框架。这两个安全框架的使用者都比较多,两者各有千秋,做技术选型选型综合考虑选择合适自己团队的就行。

SpringSecurity提供了强大的身份认证和授权功能,其作为Spring生态中的一个模块,可以做到与其他组件的无缝集成。

在本篇文章中我会介绍下在项目中如何实现SpringSecurity+JWT实现项目的登录认证及授权的功能。项目环境如下:

  • JDK1.8
  • SpringBoot 2.3.12.RELEASE
  • Mysql
  • Redis
一 前后端交互流程

在开始代码展示前,我们先介绍下前后端的交互流程,如下

  1. 前端校验本地是否存储了token,如果没有存储,跳转登录页面
  2. 用户在登录界面输入账号和密码,请求登录接口,后端返回token
  3. 在后续的请求中前端需要在请求头中携带获取的token
  4. 服务端要对请求携带的token进行校验,如果token无效或者已过期,返回前端401错误码,如果是没有相关权限返回403
  5. 前端接收到401的错误码,需要清除本地保存的token并跳转至登录页面

了解了交互流程后,也就知道咱们作为服务端需要做哪些工作了,剩下的就是看跟SpringSecurity的集成工作了。

二 数据库表结构

通常在权限控制这里会涉及五张表,账号表、角色表、权限表、账号角色关系表、角色权限关系表。这里给出一份简单的简单的建表语句,大家在实际应用中结合自己的业务功能,增加相关字段即可。

1 账号表


CREATE TABLE `pe_admin` (
  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `username` varchar(50) NOT NULL DEFAULT '' COMMENT '用户名',
  `password` varchar(100) NOT NULL DEFAULT '' COMMENT '密码',
  `nickname` varchar(50) NOT NULL DEFAULT '' COMMENT '昵称',
  `avatar` varchar(255) NOT NULL DEFAULT '' COMMENT '头像',
  `email` varchar(100) NOT NULL DEFAULT '' COMMENT '邮箱',
  `latest_login_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最近登陆时间',
  `status` tinyint NOT NULL DEFAULT '0' COMMENT '状态',
  `gender` tinyint NOT NULL DEFAULT '0' COMMENT '性别 1男 2女',
  `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `modified_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '编辑时间',
  `is_delete` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_username` (`username`) USING BTREE
) ENGINE=InnoDB COMMENT='管理员表';

INSERT INTO `pe_admin` (`username`, `password`, `nickname`, `avatar`, `email`, `status`, `gender`) VALUES ('yhzb', '$2a$10$dMknjZX69EbdgpUAQIpaU.OvhbuqR.8qfCkEEpGdN0UZeW1DxCDmi', 'Bug搬运小能手', '', 'bugporter@163.com', 1, 1);

2 角色表
CREATE TABLE `pe_role` (
  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',
  `permission` varchar(50) NOT NULL DEFAULT '' COMMENT '权限',
  `remark` varchar(100) NOT NULL DEFAULT '' COMMENT '备注',
  `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `modified_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '编辑时间',
  `is_delete` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='角色表';

INSERT INTO `pe_role` (`name`, `permission`, `remark`) VALUES ('超级管理员', 'superAdmin', '超级管理员,拥有系统最高权限');

3 菜单表


CREATE TABLE `pe_menu` (
  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `type` tinyint NOT NULL DEFAULT '0' COMMENT '类型 1目录 2菜单 3按钮',
  `parent_id` int NOT NULL DEFAULT '0' COMMENT '上级ID',
  `name` varchar(50) NOT NULL DEFAULT '' COMMENT '名称',
  `title` varchar(50) NOT NULL DEFAULT '' COMMENT '标题',
  `path` varchar(50) NOT NULL DEFAULT '' COMMENT '路径',
  `icon` varchar(50) NOT NULL DEFAULT '' COMMENT '图标',
  `component` varchar(100) NOT NULL DEFAULT '' COMMENT '前端组件',
  `creator_id` int NOT NULL DEFAULT '0' COMMENT '创建人ID',
  `creator_name` varchar(50) NOT NULL DEFAULT '' COMMENT '创建人姓名',
  `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `modifier_id` int NOT NULL DEFAULT '0' COMMENT '编辑人ID',
  `modifier_name` varchar(50) NOT NULL DEFAULT '' COMMENT '编辑人姓名',
  `modified_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '编辑时间',
  `is_delete` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='菜单表';

4 账号角色关系表
CREATE TABLE `pr_admin_role` (
  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `admin_id` int NOT NULL DEFAULT '0' COMMENT '管理员ID',
  `role_id` int NOT NULL DEFAULT '0' COMMENT '角色ID',
  `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `modified_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '编辑时间',
  `is_delete` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`),
  KEY `idx_admin_role` (`admin_id`,`role_id`) USING BTREE
) ENGINE=InnoDB COMMENT='管理员角色关系表';

INSERT INTO `pr_admin_role` (`admin_id`, `role_id`) VALUES (1, 1);

5 角色权限关系表


CREATE TABLE `pr_role_menu` (
  `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID',
  `role_id` int NOT NULL DEFAULT '0' COMMENT '角色ID',
  `menu_id` int NOT NULL DEFAULT '0' COMMENT '菜单ID',
  `created_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `modified_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '编辑时间',
  `is_delete` tinyint NOT NULL DEFAULT '0' COMMENT '是否删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB COMMENT='角色菜单关系表';

执行完上面的SQL语句,数据库里也就初始化了一个账号,用户名:yhzb 密码:123456 并且拥有superAdmin权限。

三 相关代码

接下来到编写代码的阶段了,在这部分我会一点点的将相关代码进行粘贴出来,尽量做到让大家复制粘贴出来即可使用。

1 引入依赖

我们需要在项目中引入SpringSecurity和JWT的相关依赖,如下:



<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.3.12.RELEASE</version>
  <relativePath/>
</parent>

<properties>
  <jjwt.version>0.9.1</jjwt.version>
</properties

  <dependencies>
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>${jjwt.version}</version>
  </dependency>

  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
  </dependency>
  </dependencies>

2 登录接口

登录接口的请求参数是账号和密码,响应数据是JWT生成的token,代码如下:



    @ApiOperation(value = "登录", httpMethod = "POST")
    @PostMapping(value = "/login", produces = MediaType.APPLICATION_JSON_VALUE)
    public R<String> login(@Validated @RequestBody LoginReqVO loginReqVO) {
      	// 通过用户名查找账号
        Admin admin = adminService.getByUsername(loginReqVO.getUsername());
      	// 账号不存在,或者密码不匹配,抛出异常
        if (admin == null || !passwordEncoder.matches(loginReqVO.getPassword(), admin.getPassword())) {
            throw new BaseException(BaseResultCode.USERNAME_OR_PASSWORD_ERROR);
        }

      	// 将认证成功的用户存储到SpringSecurity的上下文中
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginReqVO.getUsername(), loginReqVO.getPassword());
        Authentication authenticate = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
        SecurityContextHolder.getContext().setAuthentication(authenticate);
				
      	// 生成JWT TOKEN
        TokenUserDTO tokenUserDTO = new TokenUserDTO();
        tokenUserDTO.setUsername(loginReqVO.getUsername());
        tokenUserDTO.setUuid(UUIDUtil.createUUID());
        tokenUserDTO.setTimestamp(System.currentTimeMillis());
      
        String token = TokenUtil.generateToken(tokenProperties.getSecret(), tokenUserDTO);
				// 将TOKEN存储到Redis中,用于实现TOKEN的实效和续期
        String tokeCacheKey = RedisConstant.TOKEN_PREFIX + token;
        yhzbRedisClient.set(tokeCacheKey, admin, tokenProperties.getExpiration());

        return R.success(tokenProperties.getPrefix() + token);
    }

在登录接口中需要处理的逻辑如下:

  • 校验账号和密码
  • 将认证成功的用户添加到SpringSecurity的上下文中
  • 生成TOKEN
  • 将TOKEN保存到Redis中,并返回给前端

这里将TOKEN保存到Redis中,是因为在我的示例中,我没有使用JWT自己的过期时间,而是通过Redis的过期时间来实现TOKEN的过期时间。这样设计的目的是因为在后面的请求中,我会对合法的TOKEN进行续期操作,很多场景使用的是双TOKEN的形式实现的续期功能,个人感觉使用Redis的形式更为简单。

3 TokenUtil代码

TokenUtil类中提供了,创建TOKEN和解析TOKEN的方法,代码如下:



@Slf4j
public class TokenUtil {
    /**
     * 创建jwt token
     *
     * @param secret  签名
     * @param userDTO 用户信息
     * @return token
     */
    public static String generateToken(String secret, TokenUserDTO userDTO) {
        String token = Jwts.builder()
                .setSubject(userDTO.getUsername())
                .claim("uuid", userDTO.getUuid())
                .claim("timestamp", userDTO.getTimestamp())
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();

        return token;
    }

    /**
     * 从token中获取用户名
     *
     * @param secret 签名
     * @param token  token
     * @return 用户名
     */
    public static String getUsernameFromToken(String secret, String token) {
        Claims claims = getClaimsFromToken(secret, token);
        return claims != null ? claims.getSubject() : null;
    }

    private static Claims getClaimsFromToken(String secret, String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            log.warn("getClaimsFromToken exception", e);
            claims = null;
        }
        return claims;
    }
}

4 TokenProperties代码

这是一个Token相关的配置类,一下相关的配置存放到配置中心里,修改也方便,其源码如下:



@Setter
@Getter
@Configuration
@ConfigurationProperties(prefix = "token")
public class TokenProperties implements Serializable {
    /**
     * TOKEN的header头
     */
    private String header = "Authorization";
    /**
     * 返回给前端的TOKEN前缀
     */
    private String prefix = "Bearer ";
    /**
     * 生成TOKEN的密钥
     */
    private String secret = "YHZB";
    /**
     * TOKEN有效时长 S
     */
    private Long expiration = 7 * 24 * 3600L;
}

5 登出接口

登出结构逻辑很简单,从请求头中获取到TOKEN,然后从Redis中删除即可,代码如下:



    @ApiOperation(value = "退出登录", httpMethod = "POST")
    @PostMapping(value = "/logout", produces = MediaType.APPLICATION_JSON_VALUE)
    public R<Void> logout() {
        try {
            String token = this.getToken();

            String tokenKey = RedisConstant.TOKEN_PREFIX + token;

            yhzbRedisClient.del(tokenKey);
        } catch (Exception ignore) {

        }
        return R.success();
    }

    /**
     * 获取token
     *
     * @return token
     */
    protected String getToken() {
        String token = request.getHeader(tokenProperties.getHeader());
        if (StringUtils.isBlank(token) || !token.startsWith(tokenProperties.getPrefix())) {
            throw new BaseException(BaseResultCode.ACCESS_TOKEN_INVALID);
        }
        token = token.replace(tokenProperties.getPrefix(), "");

        String redisKey = RedisConstant.TOKEN_PREFIX + token;
        Object cacheData = yhzbRedisClient.get(redisKey);

        if (cacheData == null) {
            throw new BaseException(BaseResultCode.ACCESS_TOKEN_INVALID);
        }

        return token;
    }

6 UserDetails和UserDetailsService实现类

UserDetails和UserDetailsService是SpringSecurity中的两个核心类,他们在用户认证过程中发挥着至关重要的作用。

UserDetails是SpringSecurity中用户表示用户信息的接口,SpringSecurity可以通过该类获取到登录用户的账号、密码、角色及权限等信息。

UserDetailsService是SpringSecurity中用于加载用户信息的接口,在该接口中定义了一个loadUserByUsername的方法,我们可以在该方法中通过用户名从数据库中获取用户的基本信息及权限,并将其组装成一个UserDetails对象返回。

6.1 自定义UserDetails


@Setter
@Getter
public class YhzbAuthUser implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private String nickname;
    private Integer gender;
    private String avatar;
    private String email;
    private Collection<SimpleGrantedAuthority> authorities;

    public YhzbAuthUser() {

    }

    public YhzbAuthUser(Integer id, String username, String password, String nickname, Integer gender,
                        String avatar, String email, Collection<SimpleGrantedAuthority> authorities) {
        this.id = id;
        this.username = username;
        this.password = password;
        this.nickname = nickname;
        this.gender = gender;
        this.avatar = avatar;
        this.email = email;
        this.authorities = authorities;
    }

    public YhzbAuthUser(YhzbAuthUserBuilder builder) {
        this.username = builder.username;
        this.password = builder.password;
        this.authorities = builder.authorities;
        this.id = builder.id;
        this.nickname = builder.nickname;
        this.gender = builder.gender;
        this.avatar = builder.avatar;
        this.email = builder.email;
    }

    @Override
    @JsonIgnore
    public boolean isAccountNonExpired() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isEnabled() {
        return true;
    }

    public static class YhzbAuthUserBuilder {
        private Integer id;
        private final String username;
        private final String password;
        private String nickname;
        private Integer gender;
        private String avatar;
        private String email;
        private Collection<SimpleGrantedAuthority> authorities;

        public YhzbAuthUserBuilder(String username, String password, Collection<SimpleGrantedAuthority> authorities) {
            this.username = username;
            this.password = password;
            this.authorities = authorities;
        }

        public YhzbAuthUserBuilder withId(Integer id) {
            this.id = id;
            return this;
        }

        public YhzbAuthUserBuilder withNickname(String nickname) {
            this.nickname = nickname;

            return this;
        }

        public YhzbAuthUserBuilder withGender(Integer gender) {
            this.gender = gender;

            return this;
        }

        public YhzbAuthUserBuilder withAvatar(String avatar) {
            this.avatar = avatar;

            return this;
        }

        public YhzbAuthUser build() {
            return new YhzbAuthUser(this);
        }
    }
}

6.2 自定义UserDetailsService


@Slf4j
@Service
public class YhzbUserDetailServiceImpl implements UserDetailsService {
    @Resource
    private IAdminService adminService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Admin admin = adminService.getByUsername(username);
        if (admin == null) {
            throw new BaseException(BaseResultCode.USERNAME_OR_PASSWORD_ERROR);
        }

        List<SimpleGrantedAuthority> authorityList = new ArrayList<>();

        List<String> permissionList = adminService.getPermissionList(admin.getId());
        if (CollectionUtils.isNotEmpty(permissionList)) {
            permissionList.forEach(permission -> authorityList.add(new SimpleGrantedAuthority(permission)));
        }

        YhzbAuthUser.YhzbAuthUserBuilder builder = new YhzbAuthUser.YhzbAuthUserBuilder(admin.getUsername(), admin.getPassword(), authorityList)
                .withId(admin.getId())
                .withNickname(admin.getNickname())
                .withAvatar(admin.getAvatar())
                .withGender(GenderEnum.MALE.getCode());

        return builder.build();
    }
}

7 TokenFilter

该类用于拦截请求校验TOKEN,对合法的TOKEN进行放行并进行续期,其源码如下:



@Slf4j
public class TokenFilter extends OncePerRequestFilter {
    private final YhzbRedisClient yhzbRedisClient;

    private final TokenProperties tokenProperties;

    private final UserDetailsService userDetailsService;

    public TokenFilter(YhzbRedisClient yhzbRedisClient, TokenProperties tokenProperties, UserDetailsService userDetailsService) {
        this.yhzbRedisClient = yhzbRedisClient;
        this.tokenProperties = tokenProperties;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String requestURI = request.getRequestURI();
        String header = request.getHeader(tokenProperties.getHeader());
        if (StringUtils.isBlank(header) || !header.startsWith(tokenProperties.getPrefix())) {
            filterChain.doFilter(request, response);
            return;
        }
        String token = header.replace(tokenProperties.getPrefix(), "");
        String redisKey = RedisConstant.TOKEN_PREFIX + token;
        Object object = yhzbRedisClient.get(redisKey);
        if (object != null) {
            String username = TokenUtil.getUsernameFromToken(tokenProperties.getSecret(), token);

            UserDetails userDetails = userDetailsService.loadUserByUsername(username);

            yhzbRedisClient.expire(redisKey, tokenProperties.getExpiration());

            if (userDetails != null) {
                UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                SecurityContextHolder.getContext().setAuthentication(authenticationToken);

                log.info("TokenFilter authentication for 【{}】, 【{}】", authenticationToken.getName(), requestURI);
            }
        }
        filterChain.doFilter(request, response);
    }
}

8 认证或鉴权失败处理类


@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        // token校验失败 返回401
        String message = e == null ? "unauthorized" : e.getMessage();
        httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED, message);
    }
}

public class JwtAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        // 访问没有授权的接口 返回403
        httpServletResponse.sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());
    }
}

这里需要注意一点,如果在使用了全局异常处理器的项目中,返回的是业务自定义的错误码。想要http状态码是403的话,只需在全局异常处理器中添加如下内容:



    @ExceptionHandler(AccessDeniedException.class)
    public R<Object> handlerAccessDeniedException(AccessDeniedException exception) {
        // return R.fail(RiseCommonResultCode.NOT_PERMISSION, "没有权限进行该操作");
        throw exception;
    }

9 Security配置类

该类是对接中的关键配置类,在这里我们配置我们Web应用需要放开的接口及安全配置,我们自定义的这个类需要继承WebSecurityConfigurerAdapter并实现protected void configure(HttpSecurity http)方法,其源码如下:



@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    private static final String[] PERMIT_ALL_URL = {
            "/*.html", "/**/*.html", "/**/*.css", "/**/*.js", "webSocket/**",
            "/swagger-ui.html", "/swagger-resources/**", "/webjars/**",
            "/*/api-docs", "/v2/api-docs-ext", "/druid/**",
            "/api/auth/login", "/api/auth/logout"
    };

    @Resource
    private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;

    @Resource
    private JwtAccessDeniedHandler jwtAccessDeniedHandler;

    @Resource
    private UserDetailsService userDetailsService;

    @Resource
    private YhzbRedisClient yhzbRedisClient;

    @Resource
    private TokenProperties tokenProperties;

    @Bean
    public GrantedAuthorityDefaults grantedAuthorityDefaults() {
        return new GrantedAuthorityDefaults("");
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .addFilterBefore(tokenFilter(), UsernamePasswordAuthenticationFilter.class)
                .exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).accessDeniedHandler(jwtAccessDeniedHandler)
                .and().headers().frameOptions().disable()
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().authorizeRequests().antMatchers(PERMIT_ALL_URL).permitAll()
                .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
                .anyRequest().authenticated();
    }

    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    public TokenFilter tokenFilter() {
        return new TokenFilter(yhzbRedisClient, tokenProperties, userDetailsService);
    }
}

到这里我们的相关代码就编写完成了,我们便成功的为我们的服务增加了一层安全防护,此时未登录的用户请求未放开的接口便会返回401状态码,使用PostMan测试如下:

我们在调用登录接口获取到TOKEN:

将登录接口获取到的TOKEN添加到第一步调用接口的请求头中,便可以正常获取到数据了:

我们还可以为我们的每个接口设置一个权限,使用SpringSecurity中提供的@PreAuthorize注解添加到对应的接口上面,便可为该接口添加相应的权限,当访问该接口的用户没有对应的权限时则会返回403状态码,示例如下:

在示例中我这里有两个接口,一个接口允许具有superAdmin权限的用户访问,一个接口支持具有category:add权限的用户访问。

大家是否还记得我们初始化的用户是具有superAdmin权限的,并且在上面的测试中我们也是可以正常访问那个分页列表的接口。

我们再测试一下调用这个新增接口是否等得到符合我们所期望的结果,PostMan测试结果如下:

将superAdmin权限添加到对应接口再测测试就可以正常请求了,在上面的代码中我们使用的hasRole配置的权限,在SpringSecurity中提供了4种方法,分别如下:

  • hasAuthority
  • hasAnyAuthority
  • hasRole
  • hasAnyRole

今天的文章到这里就结束了,在这篇文章中我们介绍了下SpringSecurity的使用,看完这篇文章应该能顺利的使用SpringSecurity为我们的接口加上防护了。

实现基于Spring Boot的Web安全防护 从基本认证、表单登录到集成第三方认证服务,Spring Boot提供了丰富的功能和配置选项,帮助开发者轻松实现安全的Web应用程序。除了基本的用户名密码认证外,Spring Security还支持集成第三方认证服务,如OAuth2协议,可以通过配置实现与Google、Facebook等服务的集成,提供更多的登录选项和用户体验。Spring Boot作为Java开发者广泛使用的框架之一,提供了丰富的安全功能,可以帮助开发者轻松实现Web应用的安全防护。此外,还配置了登出功能,允许所有用户访问登出页面。 阅读详情

相关推荐

SpringSecurity的核心过滤器-CsrfFilter

跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的 Web 应用程序上执行非本意的操作的攻击方法。跟跨网站脚本(XSS)相比,XSS利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。

10年老JAVA大数据技术人的专栏 1503

Spring Security基础详细介绍

1. SpringSecurity 框架简介 1.1 概要 Spring 是非常流行和成功的 Java 应用开发框架,Spring Security正是Spring家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。 正如你可能知道的关于安全方面的两个主要区域是“认证”和“授权”(或者访问控制),一般来...

qq_36595761的博客 2754

详解spring security安全防护

主要介绍了详解spring security安全防护,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

Spring (30)如何在Spring应用中启用Spring Security

在Spring应用中启用Spring Security涉及几个关键步骤,包括引入Spring Security依赖、创建安全配置类以及配置应用的安全细节。下面,我们将深入探讨这些步骤,并通过代码示例和源码分析来详细解析如何启用和配置Spring Security。

qq_43012298的博客 963

融合ZILN损失与深度学习的LTV预测:从概率分布到精准营销决策

本文探讨了融合ZILN损失与深度学习的LTV预测方法,通过深度概率模型精准预测用户生命周期价值(LTV),优化营销决策。文章详细解析了ZILN损失函数的优势、模型架构设计要点及工程化落地实践,帮助企业在预算分配、用户分层等场景实现数据驱动的精准营销。

weixin_29093169的博客 308

车载测试实战:adb logcat日志高效抓取与问题定位技巧

本文详细介绍了车载测试中adb logcat日志的高效抓取与问题定位技巧。作为车载测试的“第一现场”,adb logcat能实时、精准地抓取系统与应用日志,是定位应用崩溃、ANR及功能异常等问题的关键工具。文章从环境搭建、核心命令组合拳到实战分析与自动化脚本,提供了完整的操作指南,帮助测试人员快速掌握这一必备技能,大幅提升问题排查效率。

ii567的博客 1043

Spring Security提供了多种安全防护功能,包括认证、授权、防护攻击

对于生产环境,通常需要从数据库或其他服务获取用户详细信息。实现接口可以让你自定义用户的加载逻辑。// 从数据库加载用户信息 return new User(username , password , authorities);} }// 从数据库加载用户信息 return new User(username , password , authorities);} }

keyboard专栏 871

05、基于ALV表报实现复选框、全选、取消全选等功能

基于Function ALV实现复选框、全选及取消全选等功能

wo520yl1314的博客 2372

Spring Boot Security自带功能齐全的HttpFirewall防火墙

在这篇文章中,我将解释 Spring 的 HttpFirewall 是什么,它提供的安全保护类型以及它在 Spring 的 Security 中的位置。此功能,因为它被认为具有极高的风险。在讨论 Spring Security 时,通常会非常关注 Spring 的安全过滤器(这是理所当然的),但不明显的是 HttpFirewall 是我们的第一道防线。基本上,Http 防火墙实施的规则会强制增加安全性,以净化和保护我们免受可能有害的精心制作的 URL 的侵害,甚至在它们到达安全过滤器链之前。...

allway2的博客 3398

elmos 524系列芯片笔记

芯片的工作工作原理:集成芯片Elmos524.03有发射回路和接收回路,手册有关的内部原理图,大致如下: 芯片会产生固有频率的电压信号,信号通过引脚DRV1、2输出给外部压电陶瓷片,产生固定频率的机械波,也就是所谓的超声波,超声波遇到障碍物时会反射,反射信号经过AINS、D引脚进入芯片,在芯片内部先后经过GAIN放大,滤波然后进入比较器,滤波后的信号(signal)同预先设定的阈值(threshold)比较产生信号输出。此信号最终会连接MCU处理器,需要重点关注。 基于这个内部的工作原

qq_38230916的博客 2490

睿尔曼超轻量仿人机械臂--机械臂接口介绍

本文介绍了睿尔曼超轻量仿人机械臂的控制器接口及末端接口。

realman_Rop的博客 3440

使用Spring Security保护网站安全,打造用户权限管理系统

Spring Security是一个基于Spring框架的安全框架,它提供了一套完整的安全性解决方案,可用于保护Java应用程序的Web和非Web部分。可以通过自定义登录页面和认证逻辑实现更灵活的用户认证方式。

BXA 944

[Dify实战] 品牌舆情速览:多渠道抓取、聚类情绪、生成日报-带绑定资源

摘要: 品牌舆情监测面临多渠道信息分散、手工处理效率低的痛点。Dify舆情速览助手通过自动化流程(数据抓取→情绪聚类→日报生成)帮助快速识别热点与风险。方案核心包括: 结构化处理:按情绪(正/中/负)聚类,分级标注风险事件; 日报输出:含热点摘要、趋势分析及处置建议(动作+团队+时间窗口); 质量控制:数据去噪、黑名单过滤,结合人工复核确保合规性。 落地需明确数据源分级(如权威媒体优先)、设置情绪阈值,并建立"发现-响应-复盘"闭环机制,提升舆情应对效率。 (字数:150)

技术小甜甜:传播可落地技术,帮助个人和企业降低解决真实场景问题的成本 300

SpringSecurity入门

SpringSecurity是一个功能强大且高度可定制的身份验证和访问控制框架。它是用于保护基于Spring的应用程序的实际标准。SpringSecurity是一个框架,致力于为Java应用程序提供身份验证和授权。与所有Spring项目一样,SpringSecurity的真正强大之处在于可以轻松扩展以满足自定义要求.1.认证用户登录,解决的是"你是谁?"2.授权判断用户拥有什么权限,可以访问什么资源.解决的是"你能干什么?"3.安全防护,防止跨站请求,session攻击等。...

凉茶铺的博客 1010

Qt C++ 串口调试助手

1. **串口配置**:支持自动检测可用串口,可配置波特率(9600~921600)、数据位(5~8)、校验位(无/奇/偶/标记/空格)、停止位(1/1.5/2)。- HEX 模式发送时,输入格式需为十六进制字符(0-9、A-F),可带空格(如 `01 A3 FF`)或不带空格(如 `01A3FF`)。// 发送模式:true=HEX,false=ASCII。// 字符串转HEX字节数组。ui->parity->addItems({"无", "奇校验", "偶校验", "标记校验", "空格校验"});

寒寒豆豆软件公司的博客 148

【华为OD机试真题】458、堆内存申请 | 机试真题+思路参考+代码解析(CD卷)(C++、Java、Py)

题目描述 >有一个总空间为100字节的堆,现要从中新申请一块内存,内存分配原则为优先紧接着前一块已使用内存分配空间足够且最接近申请大小的空闲内存。

KJ.JK 1万+

SpringBoot安全防护三部曲:JWT+OAuth2+SpringSecurity最佳实践

在当今数字化时代,Web应用的安全问题至关重要。Spring Boot作为一款广泛应用的Java开发框架,为开发者提供了便捷的开发体验。而JWT(JSON Web Token)、OAuth2(开放授权协议2.0)和Spring Security则是保障Spring Boot应用安全的强大工具。本文将详细介绍如何结合这三者,实现Spring Boot应用的安全防护,为技术人员提供最佳实践方案。

梵心白莲的博客 1268

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

SpringSecurity认证授权

RBAC模型详解,SpringSecurity入门到精通一文搞定

qq_49474843的博客 1861

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

SpringSecurity OAuth2总结

1.Spring Security的认识 1.Spring Security的核心功能 1.Authentication:身份的验证,用户登陆的验证; 2.Authorization:访问授权,授权资源的访问权限; 3.安全防护、防止跨站请求、session攻击; 4.Spring Security对OAuth的支持更友好; 5.Spring Security在网络安全方面下了更多的功夫 ...

chiyunhouse的博客 2217

温度PID控制PLC程序

温度PID稳定控制程序,适用于欧姆龙PLC,对其他PLC也具有很大参考价值

SpringSecurity基础入门应用大全

SpringSecurity基础场景使用

u011230386的博客 2468

SpringBoot安全防护指南:整合SpringSecurity防止XSS、SQL注入

Spring Security是Spring生态系统中用于提供安全认证和授权的框架,通过将其与Spring Boot整合,我们可以有效地防止这些安全漏洞。本文将详细介绍如何在Spring Boot项目中整合Spring Security,并利用它来防止XSS和SQL注入攻击。

梵心白莲的博客 2041

如何使用SpringSecurity

如何使用Spring Security

weixin_41553701的博客 4671

【SpringSecurity】认证与鉴权框架SpringSecurity——认证

认证与鉴权框架SpringSecurity——认证

低调做人,低调编码,神码都是浮云 1448

SpringSecurity-从认证到授权:构建企业级安全防护体系

本文深入解析SpringSecurity的核心架构与企业级安全防护体系,从认证到授权提供完整解决方案。详细介绍了JWT认证方案、RBAC权限模型深度实践以及高级安全策略定制,帮助开发者构建高效的企业级安全防护体系。

weixin_30642869的博客 1905

SpringSecurity学习笔记---SpringSecurity简介

SpringSecurity学习笔记—SpringSecurity简介 1.简介 Spring Security是一个功能强大且高度可定制的身份验证和访问控制框架。 它是用于保护基于Spring的应用程序的实际标准。 Spring Security是一个框架,致力于为Java应用程序提供身份验证和授权。 和所有Spring项目一样,Spring Security的真正强大之处在于可以轻松扩展以满足自定义要求. 2.SpringSecurity对比Shiro Shiro是一个强大而灵活的开源安全框架,能

qq_39314972的博客 381
上一篇: 用Java Executors创建线程池的9种方法
下一篇: SpringBoot自动装配、启动流程及自定义starter
加班不秃顶
博客等级 码龄7年 1822粉丝 599原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值