Spring 全家桶之 Spring Security(一)

2022-08-19 15:25:20 浏览数 (1)

Spring Security

Introduction

  Spring Security是基于Spring的安全框架,Spring Security提供全面的安全性解决方案,同时在Web Request和Method处理身份认证和授权,在Spring Framework基础上,Spring Security充分利用了Soring的 DI和AOP特性,为应用系统提供了声明式的安全访问控制功能,是一个轻量级的框架,可以很好的与Spring及Spring MVC集成

核心功能

  1. 认证(Who are you?)
  2. 授权(What you can do?)原理   基于Servlet Filter AOP实现认证和授权

Spring Security 最佳实践

使用系统自定义用户及yml中自定义的用户进行登录

  1. 创建Maven项目
  2. 加入依赖,SpringBoot web stater和security-stater
代码语言:javascript复制
<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-parent</artifactId>
    <version>2.1.5.RELEASE</version>
</parent>

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

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

3.创建启动SpringBoot启动类

代码语言:javascript复制
@SpringBootApplication
public class SecurityApplication {

    public static void main(String[] args) {

        SpringApplication.run(SecurityApplication.class,args);
    }
}

4.创建controller包及Contrller控制器

代码语言:javascript复制
@RestController
public class HelloSecurityController {

    @GetMapping("/security")
    public String helloSecurity(){
        return "Hello Spring Security!";
    }
}

5.启动SecurityApplication,控制台会生成密码,请看下图所示

浏览器地址栏输入http://localhost:8080/

输入用户名user及控制台生成的密码,即可登录系统访问HelloSecurityController

如果密码输入错误,则会有相应的提示

6.以上用户名密码都是由系统自动生成的,如果需要自定义用户名密码则需要在配置文件中进行配置,重新启动,输入设置的用户名密码即可登录

代码语言:javascript复制
spring:
  security:
    user:
      name: admin
      password: admin

7.关闭登录验证对启动类进行修改,{}中可以放入多个配置类

代码语言:javascript复制
@SpringBootApplication(exclude = {SecurityAutoConfiguration.class})
public class SecurityApplication {

    public static void main(String[] args) {

        SpringApplication.run(SecurityApplication.class,args);
    }
}

使用设置在内存中的用户进行登录

&emsp;&emsp;继承WebSecurityConfigurerAdapter,重写configure方法来控制安全管理的内容,将重写的类交由Spring IOC进行管理,可以自定义认证功能,重写是需要使用两个注解@Configuration和@EnableWebSecurity

代码语言:javascript复制
@Configuration//表示该类是一个配置类,返回值是Java对象,由SpringIOC管理,相当于配置xml文件
@EnableWebSecurity //表示启用SpringSecurity安全框架功能
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        PasswordEncoder pe = passwordEncoder();

        // 设置内存中用户名和密码
        auth.inMemoryAuthentication().withUser("IronMan").password(pe.encode("12345")).roles();
        auth.inMemoryAuthentication().withUser("SpiderMan").password(pe.encode("12345")).roles();
        auth.inMemoryAuthentication().withUser("Thor").password(pe.encode("thor")).roles();
    }

    // 密码加密类,该类由SpringIOC进行管理,id默认为passwordEncoder
    @Bean
    public PasswordEncoder passwordEncoder(){
        // 实现密码加密
        return new BCryptPasswordEncoder();
    }
}

启动前关闭启动类上的exclude中的内容,启动成功后使用设置的用户名密码进行登录系统,如果改配置类中设置的密码没有加密会报错“java.lang.IllegalArgumentException: There is no PasswordEncoder mapped for the id "null"”

基于ROLE的身份认证

修改config包中的MyWebSecurityConfig类,给用户设置角色,代码如下:

代码语言:javascript复制
/**
 * prePostEnabled = true表示可以使用@PreAuthorize注解和@PostAuthorize方法级别的注解
 */
@Configuration//表示该类是一个配置类,返回值是Java对象,由SpringIOC管理,相当于配置xml文件
@EnableWebSecurity //表示启用SpringSecurity安全框架功能
@EnableGlobalMethodSecurity(prePostEnabled = true) //启用方法级别的安全控制
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        PasswordEncoder pe = passwordEncoder();

        // 设置内存中用户名和密码,并设置角色,一个用户可以有多个角色
        auth.inMemoryAuthentication().withUser("IronMan").password(pe.encode("12345")).roles("admin");
        auth.inMemoryAuthentication().withUser("SpiderMan").password(pe.encode("12345")).roles("user");
        auth.inMemoryAuthentication().withUser("Thor").password(pe.encode("thor")).roles("user","admin");
    }

    // 密码加密类,该类由SpringIOC进行管理,id默认为passwordEncoder
    @Bean
    public PasswordEncoder passwordEncoder(){
        // 实现密码加密
        return new BCryptPasswordEncoder();
    }
}

修改HelloSecurityController,定义角色访问路径

代码语言:javascript复制
@RestController
public class HelloSecurityController {

    @GetMapping("/security")
    public String sayHello(){
        return "使用内存中的用户信息进行认证";
    }

    //指定user和admin都可以访问的方法
    @GetMapping("/hello")
    @PreAuthorize(value = "hasAnyRole('admin','user')")
    public String helloUserAndAdmin(){
        return "user和admin角色都可以访问";
    }

    @GetMapping("/admin")
    @PreAuthorize(value = "hasAnyRole('admin')")
    public String helloAdmin(){
        return "只有admin可以访问";
    }
}

启动应用,使用IronMan/12345访问/hello

访问/admin路径

重启tomcat,使用Thor/thor访问/hello

访问/admin

实现了不同的角色拥有不同路径的访问权限

基于JDBC的用户认证

首先修改pom.xml,增加MySQL依赖及JPA

代码语言:javascript复制
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.18</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

创建实体类UserInfo,添加get/set方法

代码语言:javascript复制
@Entity
public class UserInfo {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String username;
    private String password;
    private String role;
}

创建持久层UserInfoDao,继承JpaRepository,定义方法findByUsername(String username)

代码语言:javascript复制
public interface UserInfoDao extends JpaRepository<UserInfo,Long>{

    //按照username查询数据库
    UserInfo findByUsername(String username);
}

创建service层UserInfoService接口,并创建UserInfoServiceImpl实现类实现该接口

代码语言:javascript复制
public interface UserInfoService {

    UserInfo findUserInfo(String username);
}

不要忘记@Service注解

代码语言:javascript复制
@Service
public class UserInfoServiceImpl implements UserInfoService {

    @Autowired
    private UserInfoDao userInfoDao;

    @Override
    public UserInfo findUserInfo(String username) {
        return userInfoDao.findByUsername(username);
    }
}

配置application.properties

代码语言:javascript复制
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf8&amp;autoReconnect=true&amp;useSSL=false&amp;serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=root

spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
spring.jpa.database=mysql
# 声明创建表时使用InnoDB引擎,默认使用myisam
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect

此时启动应用会在数据库中创建user_info表,接着需要初始化user_info表中的数据 在init包下面创建JDBCInit类

代码语言:javascript复制
@Component
public class JDBCInit {

    @Autowired
    private UserInfoDao userInfoDao;

    @PostConstruct
    public void init(){

        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        UserInfo user1 = new UserInfo();
        user1.setUsername("IronMan");
        user1.setPassword(passwordEncoder.encode("12345"));
        user1.setRole("admin");

        userInfoDao.save(user1);

        UserInfo user2 = new UserInfo();
        user2.setUsername("thor");
        user2.setPassword(passwordEncoder.encode("12345"));
        user2.setRole("user");

        userInfoDao.save(user2);

    }
}

接着创建MyUserDetailService,该类继承框架包中的UserDetailService,作用类似于存储用户角色信息

代码语言:javascript复制
@Component("myUserDetailService")
public class MyUserDetailService implements UserDetailsService {

    @Autowired
    private UserInfoService userInfoService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserInfo userInfo = null;
        User user = null;
        if (username != null){
            userInfo = userInfoService.findUserInfo(username);
            if (userInfo != null){
                List<GrantedAuthority> grantedAuthorityList = new ArrayList<>();
                GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" userInfo.getRole());
                grantedAuthorityList.add(authority);
                // 创建User类,框架中的User类
                user = new User(userInfo.getUsername(),userInfo.getPassword(),grantedAuthorityList);
            }
        }
        return user;
    }
}

修改MyWebSecurityConfig,将保存用信息到内存中的代码全部删除

代码语言:javascript复制
/**
 * prePostEnabled = true表示可以使用@PreAuthorize注解和@PostAuthorize方法级别的注解
 */
@Configuration//表示该类是一个配置类,返回值是Java对象,由SpringIOC管理,相当于配置xml文件
@EnableWebSecurity //表示启用SpringSecurity安全框架功能
@EnableGlobalMethodSecurity(prePostEnabled = true) //启用方法级别的安全控制
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailService myUserDetailService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(myUserDetailService).passwordEncoder(new BCryptPasswordEncoder());
    }
}

HelloSecurityController的代码不变,启动应用,使用IronMan/12345登录,可以访问/hello,/admin

使用thor/12345登录,访问/hello,/admin

0 人点赞