权限控制与安全认证 Spring Security(一)

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 权限控制与安全认证 Spring Security

一、Spring Security介绍

Spring Security是Spring项目组提供的安全服务框架,核心功能包括认证授权。它为系统提供了声明式安全访问控制功能,减少了为系统安全而编写大量重复代码的工作。

认证

认证即系统判断用户的身份是否合法,合法可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户名密码登录、二维码登录、手机短信登录、脸部识别认证、指纹认证等方式。认证是为了保护系统的隐私数据与资源,用户的身份合法才能访问该系统的资源。

授权

授权即认证通过后,根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有权限则拒绝访问。 比如在一些视频网站中,普通用户登录后只有观看免费视频的权限,而VIP用户登录后,网站会给该用户提供观看VIP视频的权限。

认证是为了保证用户身份的合法性,授权则是为了更细粒度的对隐私数据进行划分,控制不同的用户能够访问不同的资源。

举个例子:认证是公司大门识别你作为员工能进入公司,而授权则是由于你作为公司会计可以进入财务室,查看账目,处理财务数据。

二、Spring Security认证

2.1 项目搭建

1、准备一个名为mysecurity的Mysql数据库

2、创建SpringBoot项目,添加依赖(myabtisplus需要在pom中添加依赖)

<dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.0</version>
        </dependency>

3、编写配置文件

server:
  port: 80
#日志格式
logging:
  pattern:
    console: '%d{HH:mm:ss.SSS} %clr(%-5level) ---  [%-15thread] %cyan(%-50logger{50}):%msg%n'
# 数据源
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql:///mysecurity?serverTimezone=UTC
    username: root
    password: 123456

4、在template文件夹编写项目主页面main.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>主页面</title>
</head>
<body>
<h1>主页面</h1>
</body>
</html>

5、编写访问页面控制器

@Controller
public class PageController {
    @RequestMapping("/{page}")
    public String showPage(@PathVariable String page){
        return page;
    }
}

启动项目,访问项目主页面http://localhost/main,项目会自动跳转到一个登录页面。这代表Spring Security已经开启了认证功能,不登录无法访问所有资源,该页面就是Spring Security自带的登录页面。

我们使用user作为用户名,控制台中的字符串作为密码登录,登录成功后跳转到项目主页面。

在后续的文章中,会介绍在真实开发中,如何对登录页面、登录逻辑等进行自定义配置。

2.2 内存认证

在实际开发中,用户数量不会只有一个,且密码是自己设置的。所以我们需要自定义配置用户信息。首先我们在内存中创建两个用户,Spring Security会将登录页传来的用户名密码和内存中用户名密码做匹配认证。

package com.zj.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.jaas.memory.InMemoryConfiguration;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
//配置类
@Configuration
public class SecurityConfig {
    //内存数据认证逻辑
    @Bean
    public UserDetailsService userDetailsService(){
        //1.使用内存数据进行认证
        InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();
        //2.创建两个用户
        UserDetails user1 = User.withUsername("zj").password("123").authorities("admin").build();
        UserDetails user2 = User.withUsername("zs").password("456").authorities("admin").build();
        //3.将两个用户保存到内存中
        inMemoryUserDetailsManager.createUser(user1);
        inMemoryUserDetailsManager.createUser(user2);
        return inMemoryUserDetailsManager;
    }
    //密码编码器,将密码加密保存
    @Bean
    public PasswordEncoder passwordEncoder(){
        //设置以明文保存,不加密。
        return NoOpPasswordEncoder.getInstance();
    }
}

此时进行认证测试,我们可以将登录页传来的用户名密码和内存中用户名密码做匹配认证。

2.3 UserDetailsService

在实际项目中,认证逻辑是需要自定义控制的。将UserDetailsService接口的实现类放入Spring容器即可自定义认证逻辑。InMemoryUserDetailsManager就是UserDetailsService接口的一个实现类,它将登录页传来的用户名密码和内存中用户名密码做匹配认证。当然我们也可以自定义UserDetailsService接口的实现类。

public interface UserDetailsService {
  UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}

UserDetailsService的实现类必须重写loadUserByUsername方法,该方法定义了具体的认证逻辑,参数username是前端传来的用户名,我们需要根据传来的用户名查询到该用户(一般是从数据库查询),并将查询到的用户封装成一个UserDetails对象,该对象是Spring Security提供的用户对象,包含用户名、密码、权限。Spring Security会根据UserDetails对象中的密码和客户端提供密码进行比较。相同则认证通过,不相同则认证失败。

2.4 数据库认证

1、创建数据库表

2、编写用户实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Users extends Model<Users> {
  private Integer id;
  private String username;
  private String password;
  private String phone;
}

3、编写mapper接口

public interface UsersMapper extends BaseMapper<Users> {
}

4、在 SpringBoot启动类中添加 @MapperScan 注解,扫描Mapper

@SpringBootApplication
@MapperScan("com.zj.mapper")
public class SecurityApplication {
    public static void main(String[] args) {
        SpringApplication.run(SecurityApplication.class, args);
    }
}

5、创建UserDetailsService的实现类,编写自定义认证逻辑

package com.zj.service;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.zj.pojo.Users;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
@Service
public class MyUserDetailsService implements UserDetailsService {
  //自定义认正逻辑
  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    //1.构造查询条件
    QueryWrapper<Users> query = new QueryWrapper<Users>();
    query.eq("username", username);
    //2.查询用户
    Users users = new Users();
    Users users1 = users.selectOne(query);
    if (users1 == null) {
      return null;
    }
    //3.将查询结果封装为UserDetails对象返回
    UserDetails userDetails = User.withUsername(users1.getUsername())
            .password(users1.getPassword()).authorities("admin").build();
    return userDetails;
  }
}

6、登录访问即可

2.5 PasswordEncoder

在实际开发中,为了数据安全性,在数据库中存放密码时不会存放原密码,而是会存放加密后的密码。而用户传入的参数是明文密码。此时必须使用密码解析器才能将加密密码与明文密码做比对。Spring Security中的密码解析器是PasswordEncoder

Spring Security要求容器中必须有PasswordEncoder实例,之前使用的NoOpPasswordEncoderPasswordEncoder的实现类,意思是不解析密码,使用明文密码。

Spring Security官方推荐的密码解析器是BCryptPasswordEncoder。接下来我们学习BCryptPasswordEncoder的使用。

package com.zj.security;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@SpringBootTest
public class TestPasswordEncoder {
    @Test
    public void testBCryptPasswordEncoder(){
        //创建解析器
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        //密码加密
        String pwd = bCryptPasswordEncoder.encode("12345");
        System.out.println("加密后的密码:"+pwd);
        //密码校验:明文和密文校验
        boolean matches = bCryptPasswordEncoder.matches("12345", pwd);
        System.out.println("是否校验成功:"+matches);
    }
}
加密后的密码:$2a$10$CQ0RghViS0.5I6kEpnNGeekDD44Wmt.sh876GCB6iCjjMyg39hNKe
是否校验成功:true

需要注意的是,每次加密的密文都是不一样的,但是只要是该明文加密形成的密文。都能和明文验证成功。

2.6 自定义登录页面

虽然Spring Security给我们提供了登录页面,但在实际项目中,更多的是使用自己的登录页面。Spring Security也支持用户自定义登录页面。用法如下:

1、在template目录下编写登陆页面、登录失败页面、主页面;将css文件放在static目录下面。

2、在Spring Security配置类自定义登录页面,注意该配置类要继承 WebSecurityConfigurerAdapter类。

package com.zj.config;
import org.springframework.cglib.proxy.NoOp;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //自定义表单登录
       http.formLogin()
               .loginPage("/login.html")//自定义登录页面
               .usernameParameter("username")//表单中的用户名
               .passwordParameter("password")//表单中的密码
               .loginProcessingUrl("/login")//表单提交到security的路径。
               .successForwardUrl("/main") //认证成功后跳转路径
               .failureForwardUrl("/fail");//认证失败后跳转路径
        //需要认证的资源
        http.authorizeHttpRequests()
                .antMatchers("/login.html").permitAll()//登录页不需要认证
                .anyRequest().authenticated(); //其他请求都需要认证
        //关闭CSRF防护
        http.csrf().disable();
    }
    @Override
    public void configure(WebSecurity web) throws Exception {
        //静态资源放行
        web.ignoring().antMatchers("/css/**");
    }
    //密码解析方式:不解析
    @Bean
    public PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }
}


相关文章
|
2月前
|
安全 Java 数据安全/隐私保护
使用Spring Security实现细粒度的权限控制
使用Spring Security实现细粒度的权限控制
|
2月前
|
安全 Java 数据库
实现基于Spring Security的权限管理系统
实现基于Spring Security的权限管理系统
|
2月前
|
安全 Java 数据安全/隐私保护
解析Spring Security中的权限控制策略
解析Spring Security中的权限控制策略
|
2月前
|
安全 Java 数据安全/隐私保护
使用Spring Security实现细粒度的权限控制
使用Spring Security实现细粒度的权限控制
|
2月前
|
安全 Java 数据安全/隐私保护
使用Java和Spring Security实现身份验证与授权
使用Java和Spring Security实现身份验证与授权
|
2月前
|
存储 安全 Java
Spring Security在企业级应用中的应用
Spring Security在企业级应用中的应用
|
3月前
|
存储 安全 Java
Spring Security与OAuth2集成开发
Spring Security与OAuth2集成开发
|
30天前
|
缓存 Java Maven
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
Java本地高性能缓存实践问题之SpringBoot中引入Caffeine作为缓存库的问题如何解决
|
2月前
|
Java 测试技术 数据库
Spring Boot中的项目属性配置
本节课主要讲解了 Spring Boot 中如何在业务代码中读取相关配置,包括单一配置和多个配置项,在微服务中,这种情况非常常见,往往会有很多其他微服务需要调用,所以封装一个配置类来接收这些配置是个很好的处理方式。除此之外,例如数据库相关的连接参数等等,也可以放到一个配置类中,其他遇到类似的场景,都可以这么处理。最后介绍了开发环境和生产环境配置的快速切换方式,省去了项目部署时,诸多配置信息的修改。
|
2月前
|
Java 应用服务中间件 开发者
Java面试题:解释Spring Boot的优势及其自动配置原理
Java面试题:解释Spring Boot的优势及其自动配置原理
96 0