SpringBoot集成security(2)|(security自定义配置)

SpringBoot集成security(2)|(security自定义配置)


章节
第一章链接: SpringBoot集成security(1)|(security入门)
第二章链接: SpringBoot集成security(2)|(security自定义配置)
第三章链接: SpringBoot集成security(3)|(security的前后端分离登录以及响应处理)
第四章链接: SpringBoot集成security(4)|(security基于JWT实现前后端分离自定义登录)

前言

上一章节我们简单的介绍了,springSecurity的集成,并实现了简单的登录功能,以及security登录的一些原理,本章我们将介绍security的基于数据库用户的配置,权限控制以及资源访问控制的配置

本片文章是在上一篇基础上进行的扩展,如果大家对项目基础不是很清楚请查看上一篇文章

一、Spring Security是什么?

Spring Security是一个提供身份验证,授权和保护以防止常见攻击的框架。凭借对命令式和响应式应用程序的一流支持,它是用于保护基于Spring的应用程序的事实上的标准。Spring Security 是针对Spring项目的安全框架,也是Spring Boot底层安全模块默认的技术选型,他可以实现强大的Web安全控制,对于安全控制,我们仅需要引入 spring-boot-starter-security 模块,进行少量的配置,即可实现强大的安全管理

二、springboot中security的自定义配置

要实现自定义配置,首先创建一个继承于WebSecurityConfigurerAdapter的配置类,在代码实现之前我们来了解一下WebSecurityConfigurerAdapter配置类

1、WebSecurityConfigurerAdapter介绍

WebSecurityConfigurerAdapter是一个抽象类,它实现了默认的认证授权,并且提供了三个可重写的方法供开发者自定义实现认证、授权功能。

// 自定义身份认证的逻辑
protected void configure(AuthenticationManagerBuilder auth) throws Exception { }
// 自定义全局安全过滤的逻辑
public void configure(WebSecurity web) throws Exception { }
// 自定义URL访问权限的逻辑
protected void configure(HttpSecurity http) throws Exception { }

三、内存配置用户实现

1、内存配置用户实现

思考:上一节我们讲了security的简单应用,能够配置账户,控制请求访问,其中有几个问题1、用户不能动态调整2、用户密码是明文容易泄露3、密码比对方式简单,基于以上问题我们来实现security的用户自定义管理。

2、内存用户配置

内存用户存储模式,配置如下

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 常用的三种存储方式,项目找那个用的最多的为,自定义用户存储
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //1、内存用户配置
        auth.inMemoryAuthentication().passwordEncoder(bCryptPasswordEncoder())
                .withUser("admin").password(bCryptPasswordEncoder().encode("123456")).authorities("ADMIN")
                .and()
                .withUser("test").password(bCryptPasswordEncoder().encode("123456")).authorities("TEST");
    /**
     * 使用security 提供的加密规则(还有其他加密方式)
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

注:@EnableWebSecurity注解,这个注解是Spring Security用于启用web安全的注解

3、内存配置用户小结

重写 configure(AuthenticationManagerBuilder auth)类使用inMemoryAuthentication方法(表示内存配置)配置用户,用户密码都使用了BCryptPasswordEncoder的加密方式,

注:如果密码不使用加密方式会报错:Encoded password does not look like BCrypt

四、数据库配置用户实现

1、数据库配置用户实现

思考:上面我们讲了基于内存的方式配置用户,用户配置在服务启动的时候会加载到内存中。这种方式的存在以下缺点,服务启动后不能变更,且用户不能动态维护。因此不可取。下面我们来介绍通过数据库方式实现用户配置

2、数据库依赖引入

本例种采用了数据库JPA的方式实现,首先需要引入数据库相关依赖

        <!--数据库引入引入-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

3、编写实体类

实体类有两个一个是数据库表对应的实体User,另外一个Security对应的用户实体JwtUser,前者用户和数据库交互数据,后者用于和security交互数据。

@Data
@Entity
@Table(name = "t_user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    @Column(name = "username")
    private String username;

    @Column(name = "password")
    private String password;

    @Column(name = "role")
    private String role;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", role='" + role + '\'' +
                '}';
    }
}

@Data
public class JwtUser implements UserDetails {

    private Integer id;
    private String username;
    private String password;
    private Collection<? extends GrantedAuthority> authorities;

    public JwtUser(User user) {
        id = user.getId();
        username = user.getUsername();
        password = user.getPassword();
        authorities = Collections.singleton(new SimpleGrantedAuthority(user.getRole()));
    }

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

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

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

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

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

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

    @Override
    public String toString() {
        return "JwtUser{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", authorities=" + authorities +
                '}';
    }

}

注意:JwtUser 实体类中有几个默认的方法isAccountNonExpired(),isAccountNonExpired(), isCredentialsNonExpired(), isEnabled()这几个方法返回类型为true,主要用于security的一些初始化工作,当设置的值被改后运行登录就会出现异常。

4、用户表操作类编写

本例种采用了JPA的方式实现,代码简单省事,不需要编写接口的实现

public interface UserRepository extends CrudRepository<User, Integer> {
    /**
     * 根据用户名查询用户
     * @param username
     * @return
     */
    User findByUsername(String username);
}

注册用户,用来在数据库中添加登录用户

import com.oak.net.response.ResponseHandle;
import com.oak.net.rest.entity.dto.User;
import com.oak.net.rest.service.jpa.UserRepository;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Api(tags = {"演示相关接口"})
@RestController
@RequestMapping("/demo")
public class DemoCtrl {

    @Autowired
    private UserRepository userRepository;

    @Autowired
        private BCryptPasswordEncoder bCryptPasswordEncoder;


    @ApiOperation(value = "获取接口", notes = "获取接口")
    @GetMapping(value = "/get")
    public ResponseHandle success() {
        List list = new ArrayList<>();
        return ResponseHandle.SUCCESS("获取数据成功");
    }

    @ApiOperation(value = "注册用户", notes = "注册")
    @PostMapping("/register")
    public String registerUser(@RequestBody Map<String, String> registerUser) {
        User user = new User();
        user.setUsername(registerUser.get("username"));
        user.setPassword(bCryptPasswordEncoder.encode(registerUser.get("password")));
        user.setRole("ROLE_USER");
        User save = userRepository.save(user);
        return save.toString();
    }
}

5、编写UserDetailsService实现类

UserDetailsService为securit提供接口,该接口提供了一个loadUserByUsername()方法,方法用于实现从数据库中根据用户名查询用户信息并传递给spring security,传递的形式就是使用上文说的UserDetails实体。

下图梳理一下UserDetailsService在securit架构中的作用
在这里插入图片描述
Spring Security中进行身份验证的是AuthenticationManager接口,ProviderManager是它的一个默认实现,但它并不用来处理身份认证,而是委托给配置好的AuthenticationProvider,每个AuthenticationProvider会轮流检查身份认证。检查后或者返回Authentication对象或者抛出异常。UserDetailsService用在AuthenticationProvider的实现类中查询用户信息。

UserDetailsService代码实现

@Service
public class UserServiceImpl implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;

    /**
     * 
     * @param s
     * @return 实现loadUserByUsername方法,根据用户名查找用户信息
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(s);
        return new JwtUser(user);
    }
}

6、配置数据库用户实现

WebSecurityConfigurerAdapter自定义实现类中需要重写configure(AuthenticationManagerBuilder auth)方法,和上面的内存配置一箱,只是配置方法不同。配置如下:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserServiceImpl userService;

    /**
     * 常用的三种存储方式,项目找那个用的最多的为,自定义用户存储
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //3、自定义用户存储
        auth.userDetailsService(userService)
                .passwordEncoder(bCryptPasswordEncoder());
    }
    /**
     * 使用security 提供的加密规则(还有其他加密方式)
     *
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }

}

7、调用接口验证

在浏览器访问接口http://localhost:8080/demo/get,会跳转到http://localhost:8080/login页面,在登录页面输入user用户(自己初始化到数据库中的用户),会提示访问成功。

在这里插入图片描述

8、数据库配置用户小结

本步骤实现了在数据库中添加用户,并能够实现登录认证。

五、资源访问配置实现

1、全局资源访问配置

思考:上面我们实现了请求访问控制需要认证,但是有些资源并不重要,我们不想所有的都需要认证后才能访问,例如静态资源、swagger资源等。接下来我们会完成怎么去掉这些静态资源的访问控制。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	// 前面小结配置此处省略
       /**
     * configure(WebSecurity)用于影响全局安全性(配置资源,设置调试模式,通过实现自定义防火墙定义拒绝请求)的配置设置。
     * 一般用于配置全局的某些通用事物,例如静态资源等
     * @param web
     */
    public void configure(WebSecurity web) {
        web.ignoring()
                .antMatchers(HttpMethod.OPTIONS, "/**")  ///跨域请求预处理
                .antMatchers("/favicon.ico")
                .antMatchers("/swagger**")   // 以下swagger静态资源、接口不拦截
                .antMatchers("/doc.html")
                .antMatchers("/swagger-resources/**")
                .antMatchers("/v2/api-docs")
                .antMatchers("/webjars/**")
                .antMatchers("/test/**");
    }
}

2、接口拦截配置

思考:上面我们实现了静态资源以及共用资源的访问控制放开,但是有一些接口我们也希望能够做一些定制配置,例如删除接口我们希望ADMIN用户才能访问,查询接口不登录也能访问。下面我们来实现接口的相关配置。

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	// 前面小结配置此处省略
    /**
     * 配置接口拦截
     * configure(HttpSecurity)允许基于选择匹配在资源级配置基于网络的安全性,
     * 也就是对角色所能访问的接口做出限制
     * @param httpSecurity 请求属性
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests()
                .antMatchers(HttpMethod.GET, "/demo/get").permitAll()
                //指定权限为ROLE_ADMIN才能访问,这里和方法注解配置效果一样,但是会覆盖注解
                .antMatchers("/demo/delete").hasRole("ADMIN")
                // 所有请求都需要验证
                .anyRequest().authenticated()
                .and()
                //.httpBasic() Basic认证,和表单认证只能选一个
                // 使用表单认证页面
                .formLogin()
                //配置登录入口,默认为security自带的页面/login
                .loginProcessingUrl("/login")
                .and()
                // post请求要关闭csrf验证,不然访问报错;实际开发中开启,需要前端配合传递其他参数
                .csrf().disable();
    }

3、接口请求响应示例

我们访问localhost:8080/demo/delete接口,页面会跳转到localhost:8080/login,当我们用配置的普通用户登陆时页面会出现403禁止访问的信息,这是应为我们给该请求配置了.hasRole(“ADMIN”),只有登陆用户有ADMIN角色才能访问,换成admin用户登陆,会提示成功。

在这里插入图片描述

4、认证请求响应自定义

思考:上面的请求异常很暴力,摆出一堆状态码,非开发人员很难明白到底发生了什么,我们可以自定义一个友好的提示信息,这样用户就可以很直白的感受到哪里出错。下一章节我们将实现用户的自定义登录,以及登录异常的自定义处理。

总结

到此springboot集成了security完成了用户的数据库配置登录,以及接口资源的访问权限控制。如果项目为前后端一体的,现在基本能满足一个小型项目的认证授权功能了。

第一章链接: SpringBoot集成security(1)|(security入门)
第二章链接: SpringBoot集成security(2)|(security自定义配置)
第三章链接: SpringBoot集成security(3)|(security的前后端分离登录以及响应处理)
第四章链接: SpringBoot集成security(4)|(security基于JWT实现前后端分离自定义登录)

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 集成 Security、JWT 和 Redis 可以实现基于 Token 的权限认证和会话管理。 首先,需要在 pom.xml 文件中添加相应的依赖: ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.1</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> ``` 接下来,在 Spring Boot配置文件中添加 Redis 的配置和 JWT 的配置: ```yml spring: redis: host: localhost port: 6379 password: database: 0 jwt: secret: your_secret expiration: 86400 ``` 其中,jwt.secret 为 JWT 的密钥,jwt.expiration 为 Token 的有效期。 然后,需要实现一个 Security配置类,用于配置 Token 的认证和会话管理: ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsServiceImpl userDetailsService; @Autowired private JwtAuthenticationEntryPoint unauthorizedHandler; @Bean public JwtAuthenticationFilter authenticationTokenFilterBean() throws Exception { return new JwtAuthenticationFilter(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } @Override public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception { authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity .cors().and().csrf().disable() .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and() .authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll() .antMatchers("/api/auth/**").permitAll() .anyRequest().authenticated(); // 添加 JWT filter httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class); } } ``` 其中,UserDetailsServiceImpl 是自定义的用户详情服务,JwtAuthenticationEntryPoint 是自定义的 Token 无效或过期的处理器,JwtAuthenticationFilter 是自定义的 Token 认证过滤器。 最后,需要实现一个 JWT 的工具类,用于生成 Token 和解析 Token: ```java @Component public class JwtTokenUtil { @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); return Jwts.builder() .setClaims(claims) .setSubject(userDetails.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expiration * 1000)) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } public String getUsernameFromToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getSubject(); } public boolean validateToken(String token, UserDetails userDetails) { final String username = getUsernameFromToken(token); return username.equals(userDetails.getUsername()) && !isTokenExpired(token); } private Boolean isTokenExpired(String token) { final Date expiration = getExpirationDateFromToken(token); return expiration.before(new Date()); } private Date getExpirationDateFromToken(String token) { return Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody().getExpiration(); } } ``` 在需要进行 Token 认证的控制器中,可以通过 @AuthenticationPrincipal 注解获取当前登录的用户信息: ```java @RestController @RequestMapping("/api") public class UserController { @Autowired private UserService userService; @Autowired private JwtTokenUtil jwtTokenUtil; @GetMapping("/user/me") public User getCurrentUser(@AuthenticationPrincipal UserDetails userDetails) { String username = userDetails.getUsername(); return userService.findByUsername(username); } @PostMapping("/auth/login") public ResponseEntity<?> login(@RequestBody LoginRequest loginRequest) { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()) ); SecurityContextHolder.getContext().setAuthentication(authentication); String token = jwtTokenUtil.generateToken(authentication.getPrincipal()); return ResponseEntity.ok(new JwtAuthenticationResponse(token)); } } ``` 以上就是 Spring Boot 集成 Security、JWT 和 Redis 的基本配置和使用方式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值