Spring Security
Introduction
  Spring Security 是基于 Spring 的安全框架,Spring Security 提供全面的安全性解决方案,同时在 Web Request 和 Method 处理身份认证和授权,在 Spring Framework 基础上,Spring Security 充分利用了 Soring 的 DI 和 AOP 特性,为应用系统提供了声明式的安全访问控制功能,是一个轻量级的框架,可以很好的与 Spring 及 Spring MVC 集成
核心功能
- 认证(Who are you?) 
- 授权(What you can do?) 
原理
  基于 Servlet Filter AOP 实现认证和授权
Spring Security 最佳实践
使用系统自定义用户及 yml 中自定义的用户进行登录
- 创建 Maven 项目 
- 加入依赖,SpringBoot web stater 和 security-stater 
 <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 启动类
 @SpringBootApplicationpublic class SecurityApplication {
    public static void main(String[] args) {
        SpringApplication.run(SecurityApplication.class,args);    }}
   复制代码
 
4.创建 controller 包及 Contrller 控制器
 @RestControllerpublic class HelloSecurityController {
    @GetMapping("/security")    public String helloSecurity(){        return "Hello Spring Security!";    }}
   复制代码
 
5.启动 SecurityApplication,控制台会生成密码,请看下图所示
浏览器地址栏输入 http://localhost:8080/
输入用户名 user 及控制台生成的密码,即可登录系统访问 HelloSecurityController
如果密码输入错误,则会有相应的提示
6.以上用户名密码都是由系统自动生成的,如果需要自定义用户名密码则需要在配置文件中进行配置,重新启动,输入设置的用户名密码即可登录
 spring:  security:    user:      name: admin      password: admin
   复制代码
 
7.关闭登录验证对启动类进行修改,{}中可以放入多个配置类
 @SpringBootApplication(exclude = {SecurityAutoConfiguration.class})public class SecurityApplication {
    public static void main(String[] args) {
        SpringApplication.run(SecurityApplication.class,args);    }}
   复制代码
 使用设置在内存中的用户进行登录
  继承 WebSecurityConfigurerAdapter,重写 configure 方法来控制安全管理的内容,将重写的类交由 Spring IOC 进行管理,可以自定义认证功能,重写是需要使用两个注解 @Configuration 和 @EnableWebSecurity
 @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 类,给用户设置角色,代码如下:
 /** * 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,定义角色访问路径
 @RestControllerpublic 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
 <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 方法
 @Entitypublic class UserInfo {
    @Id    @GeneratedValue(strategy = GenerationType.AUTO)    private Long id;    private String username;    private String password;    private String role;}
   复制代码
 
创建持久层 UserInfoDao,继承 JpaRepository,定义方法 findByUsername(String username)
 public interface UserInfoDao extends JpaRepository<UserInfo,Long>{
    //按照username查询数据库    UserInfo findByUsername(String username);}
   复制代码
 
创建 service 层 UserInfoService 接口,并创建 UserInfoServiceImpl 实现类实现该接口
 public interface UserInfoService {
    UserInfo findUserInfo(String username);}
   复制代码
 
不要忘记 @Service 注解
 @Servicepublic class UserInfoServiceImpl implements UserInfoService {
    @Autowired    private UserInfoDao userInfoDao;
    @Override    public UserInfo findUserInfo(String username) {        return userInfoDao.findByUsername(username);    }}
   复制代码
 
配置 application.properties
 spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&autoReconnect=true&useSSL=false&serverTimezone=Asia/Shanghaispring.datasource.username=rootspring.datasource.password=root
spring.jpa.generate-ddl=truespring.jpa.show-sql=truespring.jpa.database=mysql# 声明创建表时使用InnoDB引擎,默认使用myisamspring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
   复制代码
 
此时启动应用会在数据库中创建 user_info 表,接着需要初始化 user_info 表中的数据在 init 包下面创建 JDBCInit 类
 @Componentpublic 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,作用类似于存储用户角色信息
 @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,将保存用信息到内存中的代码全部删除
 /** * 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
评论