一.SpringSecurity框架简介

  • Spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 Web 应用安全性的完整解决方案。

  • 正如你可能知道的关于安全方面的两个主要区域是“认证”和“授权”(或者访问控制),一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是 Spring Security 重要核心功能。

  • 用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码。系统通过校验用户名和密码来完成认证过程。通俗点说就是系统认为用户是否能登录。

  • 用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。通俗点讲就是系统判断用户是否有权限去做某些事情。

二.SpringSecurity与shiro

1.SpringSecurity特点

  1. 和 Spring 无缝整合。
  2. 全面的权限控制。
  3. 专门为 Web 开发而设计。
  • 旧版本不能脱离 Web 环境使用。
  • 新版本对整个框架进行了分层抽取,分成了核心模块和 Web 模块。单独
  • 引入核心模块就可以脱离 Web 环境。
  • 重量级

2.shiro特点

Apache 旗下的轻量级权限控制框架。

  1. 轻量级

Shiro 主张的理念是把复杂的事情变简单。针对对性能有更高要求的互联网应用有更好表现。

2.通用性

好处:不局限于 Web 环境,可以脱离 Web 环境使用。

缺陷:在 Web 环境下一些特定的需求需要手动编写代码定制。

SpringSecurity与shiro总结

相对于 Shiro,在 SSM 中整合 Spring Security 都是比较麻烦的操作,所以,SpringSecurity 虽然功能比 Shiro 强大,但是使用反而没有 Shiro 多(Shiro 虽然功能没有Spring Security 多,但是对于大部分项目而言,Shiro 也够用了)。自从有了 Spring Boot 之后,Spring Boot 对于 Spring Security 提供了自动化配置方案,可以使用更少的配置来使用 Spring Security。因此,一般来说,常见的安全管理技术栈的组合是这样的:

  1. SSM + Shiro
  2. Spring Boot/Spring Cloud + Spring Security

以上只是一个推荐的组合而已,如果单纯从技术上来说,无论怎么组合,都是可以运行的。

三、Spring Security过滤器

1.Spring Security中常见过滤器

  1. FilterSecurityInterceptor:是一个方法级的权限过滤器, 基本位于过滤链的最底部。
  2. ExceptionTranslationFilter:是个异常过滤器,用来处理在认证授权过程中抛出的异常。
  3. UsernamePasswordAuthenticationFilter :对/login 的 POST 请求做拦截,校验表单中用户名,密码。 alt

2.15种过滤器

  1. SpringSecurity 采用的是责任链的设计模式,它有一条很长的过滤器链。现在对这条过滤器链的 15 个过滤器进行说明:
  2. WebAsyncManagerIntegrationFilter:将 Security 上下文与 Spring Web 中用于处理异步请求映射的 WebAsyncManager 进行集成。
  3. SecurityContextPersistenceFilter:在每次请求处理之前将该请求相关的安全上下文信息加载到 SecurityContextHolder 中,然后在该次请求处理完成之后,将SecurityContextHolder 中关于这次请求的信息存储到一个“仓储”中,然后将SecurityContextHolder 中的信息清除,例如Session 中维护一个用户的安全信息就是这个过滤器处理的。
  4. HeaderWriterFilter:用于将头信息加入响应中。
  5. CsrfFilter:用于处理跨站请求伪造。\n\n(5)LogoutFilter:用于处理退出登录。
  6. UsernamePasswordAuthenticationFilter:用于处理基于表单的登录请求,从表单中获取用户名和密码。默认情况下处理来自 /login 的请求。从表单中获取用户名和密码时,默认使用的表单 name 值为 username 和 password,这两个值可以通过设置这个过滤器的 usernameParameter 和 passwordParameter 两个参数的值进行修改。
  7. DefaultLoginPageGeneratingFilter:如果没有配置登录页面,那系统初始化时就会配置这个过滤器,并且用于在需要进行登录时生成一个登录表单页面。
  8. BasicAuthenticationFilter:检测和处理 http basic 认证。
  9. RequestCacheAwareFilter:用来处理请求的缓存。
  10. SecurityContextHolderAwareRequestFilter:主要是包装请求对象 request。
  11. AnonymousAuthenticationFilter:检测 SecurityContextHolder 中是否存在Authentication 对象,如果不存在为其提供一个匿名 Authentication。
  12. SessionManagementFilter:管理 session 的过滤器
  13. ExceptionTranslationFilter:处理 AccessDeniedException 和AuthenticationException 异常。
  14. FilterSecurityInterceptor:可以看做过滤器链的出口。
  15. RememberMeAuthenticationFilter:当用户没有登录而直接访问资源时, 从 cookie里找出用户的信息, 如果 Spring Security 能够识别出用户提供的 remember me cookie,用户将不必填写用户名和密码, 而是直接登录进入系统,该过滤器默认不开启。

3.SpringSecurity 基本流程

Spring Security 采取过滤链实现认证与授权,只有当前过滤器通过,才能进入下一个过滤器: alt

绿色部分是认证过滤器,需要我们自己配置,可以配置多个认证过滤器。认证过滤器可以使用 Spring Security 提供的认证过滤器,也可以自定义过滤器(例如:短信验证)。认证过滤器要在 configure(HttpSecurity http)方法中配置,没有配置不生效。

下面会重点介绍以下三个过滤器:UsernamePasswordAuthenticationFilter 过滤器:该过滤器会拦截前端提交的 POST 方式的登录表单请求,并进行身份认证。ExceptionTranslationFilter 过滤器:该过滤器不需要我们配置,对于前端提交的请求会直接放行,捕获后续抛出的异常并进行处理(例如:权限访问限制)。FilterSecurityInterceptor 过滤器:该过滤器是过滤器链的最后一个过滤器,根据资源权限配置来判断当前请求是否有权限访问对应的资源。如果访问受限会抛出相关异常,并由 ExceptionTranslationFilter 过滤器进行捕获和处理。

4.SpringSecurity

认证流程\n认证流程是在 UsernamePasswordAuthenticationFilter 过滤器中处理的,具体流程如下所示:alt

四、PasswordEncoder 接口

String encode(CharSequence rawPassword);
 
// 表示验证从存储中获取的编码密码与编码后提交的原始密码是否匹配。如果密码匹
配,则返回 true;如果不匹配,则返回 false。第一个参数表示需要被解析的密码。第二个
参数表示存储的密码。
boolean matches(CharSequence rawPassword, String encodedPassword);
 
// 表示如果解析的密码能够再次进行解析且达到更安全的结果则返回 true,否则返回
false。默认返回 false。
default boolean upgradeEncoding(String encodedPassword) {
    return false;
}

BCryptPasswordEncoder 是 Spring Security 官方推荐的密码解析器,平时多使用这个解析器。\nBCryptPasswordEncoder 是对 bcrypt 强散列方法的具体实现。是基于 Hash 算法实现的单向加密。可以通过 strength 控制加密强度,默认 10。

五、SpringBoot整合Spring Security入门

1.pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.1.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.guor</groupId>
    <artifactId>securityProject</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>securityProject</name>
    <description>Demo project for Spring Boot</description>
 
    <properties>
        <java.version>1.8</java.version>
    </properties>
 
    <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>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
 
        <!--mybatis-plus-->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.0.5</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!--lombok用来简化实体类-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
 
    </dependencies>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
 
</project>

2.application.properties

#spring.security.user.name=root
#spring.security.user.password=root
 
#mysql数据库连接
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/myd_bysj?serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root

3.SecurityConfig

 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
 
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        String password = passwordEncoder.encode("123");
        auth.inMemoryAuthentication().withUser("zs").password(password).roles("admin");
    }
 
    @Bean
    PasswordEncoder password() {
        return new BCryptPasswordEncoder();
    }
}
 
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
 
import javax.sql.DataSource;
 
@Configuration
public class UserSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserService userService;
    //注入数据源
    @Autowired
    private DataSource dataSource;
    //配置对象
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }
 
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userService(userService).passwordEncoder(password());
    }
    @Bean
    PasswordEncoder password() {
        return new BCryptPasswordEncoder();
    }
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //退出
        http.logout().logoutUrl("/logout").
                logoutSuccessUrl("/test/hello").permitAll();
 
        //配置没有权限访问跳转自定义页面
        http.exceptionHandling().accessDeniedPage("/unauth.html");
        http.formLogin()   //自定义自己编写的登录页面
            .loginPage("/on.html")  //登录页面设置
            .loginProcessingUrl("/user/login")   //登录访问路径
            .defaultSuccessUrl("/success.html").permitAll()  //登录成功之后,跳转路径
                .failureUrl("/unauth.html")
            .and().authorizeRequests()
                .antMatchers("/","/test/hello","/user/login").permitAll() //设置哪些路径可以直接访问,不需要认证
                //当前登录用户,只有具有admins权限才可以访问这个路径
                //1 hasAuthority方法
               // .antMatchers("/test/index").hasAuthority("admins")
                //2 hasAnyAuthority方法
               // .antMatchers("/test/index").hasAnyAuthority("admins,manager")
                //3 hasRole方法   ROLE_sale
                .antMatchers("/test/index").hasRole("sale")
 
                .anyRequest().authenticated()
                .and().rememberMe().tokenRepository(persistentTokenRepository())
                .tokenValiditySeconds(60)//设置有效时长,单位秒
                .userDetailsService(userService);
               // .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
          // .and().csrf().disable();  //关闭csrf防护
    }
}

4.启动类

 
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
 
@SpringBootApplication
@MapperScan("com.guor.security.mapper")
@EnableGlobalMethodSecurity(securedEnabled=true,prePostEnabled = true)
public class SecurityProjectApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(SecurityProjectApplication.class, args);
    }
 
}

5.User

 
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
 
@Data
public class User {
    private Integer id;
    private String username;
    private String password;
}

6.UserService

 
import com.guor.security.entity.User;
import com.guor.security.mapper.UsersMapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
 
import java.util.List;
 
@Service
public class UserServiceImpl implements UserService {
 
    @Autowired
    private UserMapper userMapper;
 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        //调用usersMapper方法,根据用户名查询数据库
        QueryWrapper<Users> wrapper = new QueryWrapper();
        // where username=?
        wrapper.eq("username",username);
        User user = userMapper.selectOne(wrapper);
        //判断
        if(user == null) {//数据库没有用户名,认证失败
            throw  new UsernameNotFoundException("用户名不存在!");
        }
        List<GrantedAuthority> auths =
                AuthorityUtils.commaSeparatedStringToAuthorityList("admin,ROLE_sale");
        //从查询数据库返回users对象,得到用户名和密码,返回
        return new User(user.getUsername(),
                new BCryptPasswordEncoder().encode(user.getPassword()),auths);
    }
}

7.UserMapper

 
import com.guor.security.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.springframework.stereotype.Repository;
 
@Repository
public interface UserMapper extends BaseMapper<User> {
}

8.UserController

 
import com.guor.security.entity.User;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
 
import java.util.ArrayList;
import java.util.List;
 
@RestController
@RequestMapping("/test")
public class UserController {
 
    @GetMapping("hello")
    public String hello() {
        return "hello security";
    }
 
    @GetMapping("index")
    public String index() {
        return "hello index";
    }
 
    @GetMapping("update")
    //@Secured({"ROLE_sale","ROLE_manager"})
    //@PreAuthorize("hasAnyAuthority('admins')")
    @PostAuthorize("hasAnyAuthority('admins')")
    public String update() {
        System.out.println("update......");
        return "hello update";
    }
 
    @GetMapping("getAll")
    @PostAuthorize("hasAnyAuthority('admins')")
    @PostFilter("filterObject.username == 'admin1'")
    public List<Users> getAllUser(){
        ArrayList<Users> list = new ArrayList<>();
        list.add(new Users(11,"admin1","6666"));
        list.add(new Users(21,"admin2","888"));
        System.out.println(list);
        return list;
    }
 
}

alt