0


新版SpringSecurity5.x使用与配置

一、了解SpringSecurity

1.1 什么是Spring Security?

Spring Security 是一个强大且高度可定制的身份验证和访问控制框架。它是 Spring 生态系统的一部分,为基于 Spring 的应用提供了全面的安全服务。Spring Security 的设计目标是为应用的安全需求提供一个完整的解决方案,同时保持高度的灵活性和可扩展性。

1.2 Spring Security功能

Spring Security 提供的功能包括但不限于:

  • 认证(Authentication):验证用户身份,通常需要用户名和密码。
  • 授权(Authorization):确定已认证的用户可以访问哪些资源或执行哪些操作。
  • CSRF 保护:防止跨站请求伪造攻击。
  • 会话管理:处理用户的会话,包括会话的创建、维护和销毁。
  • 加密和编码:提供加密和散列算法的支持。
  • OAuth2 和 OpenID Connect 支持:集成 OAuth2 和 OpenID Connect 协议,实现第三方认证。
  • CORS 支持:处理跨域资源共享(Cross-Origin Resource Sharing)请求。
  • 安全配置:允许通过 XML 或 Java 配置来定制安全策略。
1.3 Spring Security原理

Spring Security 的工作原理涉及几个关键组件:

  • SecurityContext:存储认证信息,如当前登录用户和他们的权限。
  • AuthenticationManager:负责用户认证,通常使用 UserDetailsService 来加载用户信息。
  • AccessDecisionManager:决定用户是否有权访问特定资源。
  • Filter Chain:一系列的过滤器处理请求和响应,例如 UsernamePasswordAuthenticationFilter 用于处理用户名和密码的提交。

1.4 RABC (Role-Based Access Control)

RABC,即基于角色的访问控制,是一种常见的访问控制机制,用于管理用户对资源的访问权限。在 RABC 中,权限不是直接授予用户,而是授予用户所属的角色。每个用户可以拥有一个或多个角色,而每个角色则有一组相应的权限。这种机制简化了权限管理,因为只需更改用户的角色就可以改变他们的权限集。


二、SpringSecurity简单案例

2.1 引入SpringSecurity依赖

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

2.2 创建一个简单的Controller

@RestController
public class HelloController {
    @GetMapping("Hello")
    public String Hello(){
        return "Hello SpringSecurity";
    }
}

2.3 运行后访问localhost:8080/Hello,会自动跳转到localhost:8080/login

这里的用户密码都在启动时候的控制台上 (注意:每一次启动密码都不一样)

用户名:user

密码: b82aa5e5-0a3a-466b-90a8-e94098877823(控制台上的一串密码)

登陆后成功访问


三、SpringSecurity配置

后面的配置都是基于小案例的基础上实现,请先完成上述的小案例

3.1 自定义用户与密码

由于每一次生成密码都是不固定的,对调试并不友好,springSecurity可以通过在application.yml中进行自定义设置用户和密码

spring:
  security:
    user:
      name: alphaMilk
      password: 123456

输入用户名和密码即可正常登陆并访问资源


3.2 允许匿名访问路径

    在Spring Security框架中,允许某些路径或资源在未经过身份验证的情况下被访问,通常称为“允许匿名访问”。这种配置对于公共页面、登录页面、注册页面、API文档等是非常必要的,因为这些页面或资源需要对所有用户开放,无论他们是否已经登录。

以下通过配置类实现,用户能够匿名访问login页面

// 使用@Configuration标记此类为Spring的配置类
@Configuration
// 启用WebSecurity的自动配置,以便Spring Security可以管理Web安全
@EnableWebSecurity
public class SecurityConfiguration {

    // 定义一个名为securityFilterChain的bean,该bean将负责构建和应用安全过滤器链
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 配置HttpSecurity对象,定义安全规则
        http
                // 授权HTTP请求,定义哪些URL需要什么类型的访问控制
                .authorizeHttpRequests((authz) -> authz
                        // 允许"/user/login" URL匿名访问
                        .requestMatchers("/user/login").anonymous()
                        
                        // 所有其他请求都需要认证才能访问
                        .anyRequest().authenticated())
                
                // 启用HTTP Basic认证,默认情况下提供简单的用户名/密码认证
                .httpBasic(Customizer.withDefaults());

        // 构建并返回SecurityFilterChain
        return http.build();
    }
}

创建一个LoginController类

@RestController
@RequestMapping("user")
public class LoginController {
    
    @GetMapping("/login")
    public String Login(){
        return "这是登陆资源页面";
    }
}

重启系统后直接访问,不需要登陆即可获取资源.


3.3 数据库实现登陆校验

通过自己数据库的用户和密码,实现登陆。将之前的自定义用户密码(application.yml中)都删除掉,并执行以下操作:

用户表单:

-- 创建一个包含用户信息和角色的简化表
CREATE TABLE IF NOT EXISTS `users` (
    `id` INT AUTO_INCREMENT PRIMARY KEY,          -- 用户ID,自增主键
    `username` VARCHAR(255) NOT NULL UNIQUE,      -- 用户名,唯一且不能为空
    `password` VARCHAR(255) NOT NULL,             -- 密码,存储加密后的密码
    `role` ENUM('ROLE_USER', 'ROLE_ADMIN') NOT NULL, -- 角色,预定义为'ROLE_USER'或'ROLE_ADMIN'
    `enabled` TINYINT(1) NOT NULL DEFAULT 1       -- 用户状态,1表示启用,0表示禁用
);

**注意:这里的role需要按照ROLE_身份 的方式进行存储以便springsecurity进行权限访问控制 **

插入案例数据 :

-- 插入示例用户数据
INSERT INTO `users` (`username`, `password`, `role`, `enabled`)
VALUES
    ('user1', '123456', 'ROLE_USER', 1),
    ('admin1', '123456', 'ROLE_ADMIN', 1),
    ('disabledUser', '123456', 'ROLE_USER', 0);

引入依赖:

<!--        数据库依赖-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.5</version>
            <exclusions>
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis-spring</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>3.0.3</version>
        </dependency>
        
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>

配置数据源:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/ap_security?characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver

创建User实体与mapper并加上启动注释

User实体

@TableName(value ="users")
@Data
public class Users implements Serializable {
    /**
     * 
     */
    @TableId(type = IdType.AUTO)
    private Integer id;

    /**
     * 
     */
    private String username;

    /**
     * 
     */
    private String password;

    /**
     * 
     */
    private Object role;

    /**
     * 
     */
    private Integer enabled;

    @TableField(exist = false)
    private static final long serialVersionUID = 1L;

    @Override
    public boolean equals(Object that) {
        if (this == that) {
            return true;
        }
        if (that == null) {
            return false;
        }
        if (getClass() != that.getClass()) {
            return false;
        }
        Users other = (Users) that;
        return (this.getId() == null ? other.getId() == null : this.getId().equals(other.getId()))
            && (this.getUsername() == null ? other.getUsername() == null : this.getUsername().equals(other.getUsername()))
            && (this.getPassword() == null ? other.getPassword() == null : this.getPassword().equals(other.getPassword()))
            && (this.getRole() == null ? other.getRole() == null : this.getRole().equals(other.getRole()))
            && (this.getEnabled() == null ? other.getEnabled() == null : this.getEnabled().equals(other.getEnabled()));
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((getId() == null) ? 0 : getId().hashCode());
        result = prime * result + ((getUsername() == null) ? 0 : getUsername().hashCode());
        result = prime * result + ((getPassword() == null) ? 0 : getPassword().hashCode());
        result = prime * result + ((getRole() == null) ? 0 : getRole().hashCode());
        result = prime * result + ((getEnabled() == null) ? 0 : getEnabled().hashCode());
        return result;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", id=").append(id);
        sb.append(", username=").append(username);
        sb.append(", password=").append(password);
        sb.append(", role=").append(role);
        sb.append(", enabled=").append(enabled);
        sb.append(", serialVersionUID=").append(serialVersionUID);
        sb.append("]");
        return sb.toString();
    }
}

UserMapper

public interface UsersMapper extends BaseMapper<Users> {

}

@MapperScan

@SpringBootApplication
@MapperScan("com.example.mysecurity.mapper")
public class MySecurityApplication {

    public static void main(String[] args) {
        SpringApplication.run(MySecurityApplication.class, args);
    }

}

测试mybatisPlus是否配置正确

@SpringBootTest
class MySecurityApplicationTests {

    @Autowired
    private UsersMapper usersMapper;

    @Test
    void contextLoads() {
        List<Users> users = usersMapper.selectList(null);
        System.out.println(users);
    }

}

通过后,即可开始实现通过自己数据库进行登陆功能:

先创建返回的验证类

LoginUser 实现 UserDetails

@Data
@NoArgsConstructor
@AllArgsConstructor
public class LoginUser implements UserDetails {

    private Users user;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public String getPassword() {
        return user.getPassword();
    }

    @Override
    public String getUsername() {
        return user.getUsername();
    }

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

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

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

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

登陆实现类

@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UsersMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //根据用户名查询用户信息
        LambdaQueryWrapper<Users> wrapper = new LambdaQueryWrapper<>();

        wrapper.eq(Users::getUsername,username);
        Users user = userMapper.selectOne(wrapper);

        //如果查询不到数据就通过抛出异常来给出提示
        if(Objects.isNull(user)){
            throw new RuntimeException("用户名或密码错误");
        }

        Collection<? extends GrantedAuthority> authorities = Collections.singletonList(new SimpleGrantedAuthority(user.getRole()));

        //封装成UserDetails对象返回 
        return new LoginUser(user,authorities);
    }
}
    由于在Spring Boot 2.3及更高版本中,Spring Security默认不再提供任何内置的
PasswordEncoder

。这意味着如果在配置中直接使用明文密码或没有正确配置

PasswordEncoder

,你将看到这个异常。这里暂时先使用明文加密。后面将一步步完善加密功能.

在SecurityConfig中加入配置

@Configuration
// 启用WebSecurity的自动配置,以便Spring Security可以管理Web安全
@EnableWebSecurity
public class SecurityConfiguration {

//    设置密码加密为明文加密
    @Bean
    public org.springframework.security.crypto.password.PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }
    
    // 定义一个名为securityFilterChain的bean,该bean将负责构建和应用安全过滤器链
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 配置HttpSecurity对象,定义安全规则
        http
                // 授权HTTP请求,定义哪些URL需要什么类型的访问控制
                .authorizeHttpRequests((authz) -> authz
                        // 允许"/user/login" URL匿名访问
                        .requestMatchers("/user/login").anonymous()
                        
                        // 所有其他请求都需要认证才能访问
                        .anyRequest().authenticated())
                
                // 启用HTTP Basic认证,默认情况下提供简单的用户名/密码认证
                .httpBasic(Customizer.withDefaults());

        // 构建并返回SecurityFilterChain
        return http.build();
    }
}

再次访问localhost:8080/Hello后弹出登陆框:

输入任意的用户与密码即可正常访问


3.4 实现角色权限访问

在Controller中定义一个Admin资源类,只有admin用户才能进行访问

@RequestMapping("admin")
@RestController
@Slf4j
public class AdminController {

    @GetMapping("resourse")
    public String AdminRole(){
        return "这是只有管理员用户才能访问的资源";
    }

}

在设置中进行配置

@Configuration
// 启用WebSecurity的自动配置,以便Spring Security可以管理Web安全
@EnableWebSecurity
public class SecurityConfiguration {

//    设置密码加密为明文加密
    @Bean
    public org.springframework.security.crypto.password.PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();
    }

    // 定义一个名为securityFilterChain的bean,该bean将负责构建和应用安全过滤器链
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 配置HttpSecurity对象,定义安全规则
        http
                // 授权HTTP请求,定义哪些URL需要什么类型的访问控制
                .authorizeHttpRequests((authz) -> authz
                        .requestMatchers("/user/login").anonymous()
//                        需要有Admin身份的用户才能进行访问
                        .requestMatchers("/admin/**").hasRole("ADMIN")
                        // 所有其他请求都需要认证才能访问
                        .anyRequest().authenticated())
                
                // 启用HTTP Basic认证,默认情况下提供简单的用户名/密码认证
                .httpBasic(Customizer.withDefaults());

        // 构建并返回SecurityFilterChain
        return http.build();
    }
}

重启服务器后分别用两种身份进行访问

用户访问:

管理员访问:


3.5 对密码进行B加密

config中进行配置BCrypt

@Configuration
// 启用WebSecurity的自动配置,以便Spring Security可以管理Web安全
@EnableWebSecurity
public class SecurityConfiguration {

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

//    设置密码加密为B加密
    @Bean
    public PasswordEncoder passwordEncoder() {
     return new BCryptPasswordEncoder();
    }

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    // 定义一个名为securityFilterChain的bean,该bean将负责构建和应用安全过滤器链
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 配置HttpSecurity对象,定义安全规则
        http
                // 授权HTTP请求,定义哪些URL需要什么类型的访问控制
                .authorizeHttpRequests((authz) -> authz
                        .requestMatchers("/user/login").anonymous()
//                        需要有Admin身份的用户才能进行访问
                        .requestMatchers("/admin/**").hasRole("ADMIN")
                        // 所有其他请求都需要认证才能访问
                        .anyRequest().authenticated())
                
                // 启用HTTP Basic认证,默认情况下提供简单的用户名/密码认证
                .httpBasic(Customizer.withDefaults());

        // 构建并返回SecurityFilterChain
        return http.build();
    }
}

由于数据库中都是明文的密码,所以这里可以通过创建一个SpringbootTest类,将所有用户的密码改为B加密后的数据.

    @Test
    public void testUpdateAllPasswords() {
        // 创建一个BCryptPasswordEncoder实例
        BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

        // 更新所有用户的密码为加密后的"123456"
        String encodedPassword = encoder.encode("123456");

        // 构造更新条件
        LambdaUpdateWrapper<Users> updateWrapper = new LambdaUpdateWrapper<>();
        updateWrapper.set(Users::getPassword, encodedPassword);

        // 执行更新操作
        boolean result = usersMapper.update(null, updateWrapper) > 0;

        if (result) {
            System.out.println("所有用户的密码更新成功!");
        } else {
            System.out.println("密码更新失败!");
        }
        
    }

配置好后,重新进行登陆查看输入对应的admin1和123456


3.6 结合Jwt实现多重校验

Spring Security 和 JSON Web Tokens (JWT) 可以协同工作来提供更灵活和安全的身份验证和授权机制。尽管 Spring Security 提供了一套全面的安全框架,但它默认使用基于会话的认证机制,这意味着服务器维护着与客户端的活动会话状态。而JWT提供了一种无状态的认证方式,这意味着每个请求都包含完整的认证信息,无需服务器保存会话状态。

pom文件中引入jwt所需要的依赖

<!--        JWT依赖-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

Jwt资源类:

@Component
@ConfigurationProperties(prefix = "jwt")
@Data
public class JwtProperties {

    private String SecretKey;
    private long Ttl;
    private String TokenName;

}

Jwt yml配置:

jwt:
  secret-key: Alphamilk
  token-name: Authorization
  ttl: 10800000

实现Jwt的工具类

@Component
public class JwtUtil {

    @Autowired
    private JwtProperties jwtProperties;

    public String createJWT(Map<String, Object> claims, long ttlMillis) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        long expMillis = System.currentTimeMillis() + ttlMillis;
        Date exp = new Date(expMillis);

        return Jwts.builder()
                .setClaims(claims)
                .signWith(signatureAlgorithm, jwtProperties.getSecretKey().getBytes(StandardCharsets.UTF_8))
                .setExpiration(exp)
                .compact();
    }

    public Claims parseJWT(String token) {
        return Jwts.parser()
                .setSigningKey(jwtProperties.getSecretKey().getBytes(StandardCharsets.UTF_8))
                .parseClaimsJws(token)
                .getBody();
    }

    public boolean isTokenValid(String token, UserDetails userDetails) {
        final String username = getUsernameFromToken(token);
        return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
    }

    private String getUsernameFromToken(String token) {
        Claims claims = parseJWT(token);
        return (String) claims.getSubject();
    }

    private boolean isTokenExpired(String token) {
        try {
            final Date expiration = parseJWT(token).getExpiration();
            return expiration.before(new Date());
        } catch (ExpiredJwtException e) {
            return true;
        }
    }
}

Jwt的校验Filter

@Component
public class JwtFilter extends OncePerRequestFilter {

    @Autowired
    private JwtProperties jwtProperties;

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtUtil jwtUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String token = request.getHeader(jwtProperties.getTokenName());

        if (token != null) {
            try {
                Claims claims = jwtUtil.parseJWT(token);
                String username = (String) claims.get("userName");

                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                    if (jwtUtil.isTokenValid(token, userDetails)) {
                        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                                userDetails, null, userDetails.getAuthorities());
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                }
            } catch (ExpiredJwtException ex) {

                response.sendError(HttpServletResponse.SC_FORBIDDEN, "Token has expired.");
            } catch (JwtException ex) {

                response.sendError(HttpServletResponse.SC_FORBIDDEN, "Invalid token.");
            }
        }else {
            response.sendError(HttpServletResponse.SC_FORBIDDEN, "No token provided.");
        }
        filterChain.doFilter(request, response);
    }
}

将jwt校验规则加入到Spring的Filter中进行校验

@Configuration
// 启用WebSecurity的自动配置,以便Spring Security可以管理Web安全
@EnableWebSecurity
public class SecurityConfiguration {

    @Autowired

    private JwtFilter jwtFilter;

    @Autowired
    private UserDetailsServiceImpl userDetailsService;

//    设置密码加密为B加密
    @Bean
    public PasswordEncoder passwordEncoder() {
     return new BCryptPasswordEncoder();
    }

    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService);
    }

    // 定义一个名为securityFilterChain的bean,该bean将负责构建和应用安全过滤器链
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        // 配置HttpSecurity对象,定义安全规则
        http
                // 授权HTTP请求,定义哪些URL需要什么类型的访问控制
                .authorizeHttpRequests((authz) -> authz
                        .requestMatchers("/user/login").anonymous()
//                        需要有Admin身份的用户才能进行访问
                        .requestMatchers("/admin/**").hasRole("ADMIN")
                        // 所有其他请求都需要认证才能访问
                        .anyRequest().authenticated())

                // 启用HTTP Basic认证,默认情况下提供简单的用户名/密码认证
                .httpBasic(Customizer.withDefaults());
//        加入jwtFIlter
        http.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
        // 构建并返回SecurityFilterChain
        return http.build();
    }
}

标签: 数据库

本文转载自: https://blog.csdn.net/dogxixi/article/details/140487200
版权归原作者 Alphamilk 所有, 如有侵权,请联系我们删除。

“新版SpringSecurity5.x使用与配置”的评论:

还没有评论