基础

核心

认证与授权

 

与Shiro联系

SpringSecurity 在 SpringBoot 出现前因为配置复杂使用较少,但是在SpringBoot 出现后搭配使用开发效率大大提高。是一款重量级框架。而 Shiro 是一款轻量级框架,配置简单一些,所以如果不使用 SpringBoot,那么一般搭配 Shiro,而使用SpringBoot 就搭配 SpringSecurity。

 

核心接口

UserDetailsService

定义了SpringSecurity 查询用户信息的接口方法,在SpringSecurity 认证时,并不是直接通过用户名密码去数据库比对,没有对应就返回,而是先通过 username 去数据库查到对应的用户信息,然后进行拼接成 SpringSecurity 内部维护的用户对象,然后由内部方法进行密码比对。而查询数据库返回用户对象的接口方法就是由 UserDetailsService 接口定义的。

 

UserDetails  

上面说到数据库查询用户信息会返回一个SpringSecurity 内部维护的用户对象。这个用户抽象类就是 UserDetails,其内部结构如下

public interface UserDetails extends Serializable {
    // ~ Methods
    // ========================================================================================================

    /**
     * Returns the authorities granted to the user. Cannot return <code>null</code>.
     * 授权列表
     * @return the authorities, sorted by natural key (never <code>null</code>)
     */
    Collection<? extends GrantedAuthority> getAuthorities();

    /**
     * Returns the password used to authenticate the user.
     *
     * @return the password
     */
    String getPassword();

    /**
     * Returns the username used to authenticate the user. Cannot return <code>null</code>.
     *
     * @return the username (never <code>null</code>)
     */
    String getUsername();

    /**
     * Indicates whether the user's account has expired. An expired account cannot be
     * authenticated.
     * 是否过期
     * @return <code>true</code> if the user's account is valid (ie non-expired),
     * <code>false</code> if no longer valid (ie expired)
     */
    boolean isAccountNonExpired();

    /**
     * Indicates whether the user is locked or unlocked. A locked user cannot be
     * authenticated.
     * 是否锁定,如果锁定就无法验证
     * @return <code>true</code> if the user is not locked, <code>false</code> otherwise
     */
    boolean isAccountNonLocked();

    /**
     * Indicates whether the user's credentials (password) has expired. Expired
     * credentials prevent authentication.
     * 用户凭证是否过期,过期的凭证会阻止身份验证
     * @return <code>true</code> if the user's credentials are valid (ie non-expired),
     * <code>false</code> if no longer valid (ie expired)
     */
    boolean isCredentialsNonExpired();

    /**
     * Indicates whether the user is enabled or disabled. A disabled user cannot be
     * authenticated.
     * 用户是启用还是禁用,无法对禁用的用户进行身份验证
     * @return <code>true</code> if the user is enabled, <code>false</code> otherwise
     */
    boolean isEnabled();
}

在使用时可以让自定义用户来实现这个接口。

 

PasswordEncoder

密码接口,一般使用 BCryptPasswordEncoder 来作为默认的密码转换器。SpringSecurity 在加密时引入盐,使得加密过程是不可逆的,而加密后的字符串包含盐信息,在比较方法中会对加密后的密码进行解析,解析出盐值,然后对输入密码进行加密,比较输入密码加密后的结果是否与原密码加密后的结果一致。使用 encode 方法进行加密, matches 方法进行密码比较。如果一致返回 true。

 

常用配置

用户名密码配置

方式一、配置文件

方式二、配置类

 方式三、自定义配置

 因为一般项目用户名密码都是存在数据库的,所以这是最主流的。

1、配置UserDetails,返回用户信息

2、添加配置类,将userDetails注册进 SpringSecurity

 

记住我

原理

在登陆后会向数据库的 persistent_logins 表中插入一条记录,表结构如下

series 是主键, 随后将 series 和 token 进行算法转换成字符串发给客户端,后面客户端会携带 Cookie ,当下次访问时后端会解析 Cookie ,解析成 series 和 token ,然后去表中匹配,验证token是否一致,以及 last_used + 存活时间是否到期,如果都满足就再以 name 走 UserDetailsService 的方法,返回用户信息。

 

配置

建表语句:

DROP TABLE IF EXISTS `persistent_logins`;
CREATE TABLE `persistent_logins` (
  `username` varchar(64) NOT NULL,
  `series` varchar(64) NOT NULL,
  `token` varchar(64) NOT NULL,
  `last_used` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`series`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
   @Resource
    private DataSource datasource;

    /**
     * 注入记住我token表的数据源
     * @return
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(datasource);
//        jdbcTokenRepository.setCreateTableOnStartup(true);      // 是否自动创建token数据表,如果是第一次可以勾选,后面表存在还开启就会报错
        return jdbcTokenRepository;
    }

可以使用在配置方法中添加 “.rememberMeParameter(“rem”) ” 配置记住我功能的name

注意:

1、这里的过期时间是拒上次打开浏览器登陆开始计算的,也就是每次打开浏览器访问一次上次登陆时间都会刷新一次。而浏览器内部访问并不会刷新时间。

2、退出后(退出登陆状态)会清除数据库的token数据,再次访问需要重新登录

 

登陆成功处理器

步骤一:增加组件

方式一、继承实现类

方式二、实现底层接口

 步骤二:将组件注册进成功处理器配置中

 

登陆失败处理器

步骤一:增加组件

方式一、实现接口

 方式二、继承实现类

 步骤二:将组件注册进失败处理器配置中

 

权限认证失败处理器

1、组件

2、配置

 

用户退出处理器

1、组件

 

 2、配置

 

 

角色权限

访问一个需要权限或角色的页面需要先登陆,如果登陆后还是不能访问就会返回500.

角色、权限、用户关系

权限与角色是多对多,角色与用户也是多对多。权限指的是对某个表具体的增删改查权限,而角色是一系列权限的集合。比如管理员角色拥有对所有表增删改查的权限,普通用户角色只拥有对所有表查询的权限,而用户 admin 拥有管理员角色,用户 A 拥有普通用户的角色。

定义权限

在config里配置路径所需权限,在UserDetailsService里配置用户所拥有的权限。

1、hasAuthority 是与关系,如果在config里配置了多个权限,如”admin,manager”,那么在UserDetailsService也必须对用户配置两个角色权限才可以访问

2、hasAnyAuthority 是或关系,如果在 config里配置了多个权限,如”admin,manager”,那么在UserDetailsService只需要对用户配置一个权限就可以访问

 

定义角色

 

角色在 UserDetailsService 实现类中配置需要加 “ROLE_” 前缀

而hasRole 和hasAnyRole 对应权限里的hasAuthority 和hasAnyAuthority,是与和或的关系。

 

Access 来定义权限、角色

上面的hasRole、hasAuthority 底层都是使用 access 来实现的,所以我们还可以通过底层的access 方法来主直接定义权限、角色。

那么 config 里配置就是如下:

 

自定义 Access 校验规则

1、组件

2、配置

 

基于 IP 来限制

这样的话只能接收来自 127.0.0.1 的请求。

 

定义角色注解

@Secured单个””里不支持使用,隔开,也就是不支持与关系。如果要配置多个或关系,可以使用{}, 在UserDetailsService里只要配置一个就可以访问。

并且只支持定义角色,不支持定义权限,也就是Secured里必须是ROLE_开头

 

定义角色、权限注解

可以定义角色、也可以定义权限

如果用户拥有的角色是abc,那么在这里可以配置hasRole(‘abc’),也可以配置hasRole(‘ROLE_abc’),而使用config配置类配置则不可以,会报错。而大小写则和配置类一样会区分

 

先执行后校验注解

可以用于记录访问日志

 

对返回和传入数据过滤注解

 

 

CSRF

CSRF 是为了防止用户在开启记住登陆后,其他非法用户截取到登陆用户的 Cookie ,登陆其他用户进行非法操作。

默认是开启的,开启后用户登录时,系统发放一个CsrfToken值(key是 _csrf,value是token值),用户携带该CsrfToken值与用户名、密码等参数完成登录。系统记录该会话的 CsrfToken 值,之后在用户的任何请求中,都必须带上该CsrfToken值,并由系统进行校验。

配置:

相关依赖:

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--对Thymeleaf添加Spring Security标签支持-->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </dependency>

开启 CSRF 时配置类不能配置 loginProcessingUrl 和 defaultSuccessUrl 。会影响登陆跳转逻辑。

 

其他配置

1、如果配置了登陆的URL(也就是loginProcessingUrl),那么自定义Controller里处理的登陆请求就会用不到,走的是SpringSecurity内部的验证方法。

2、anyRequest()必须配置在所有的antMatches后面,也就是笼统的权限配置必须放在其他权限的最后

3、and()是用于连接多个http配置。     

4、在开发时需要添加@EnableWebSecurity注解,这个注解会自动配置安全认证策略和认证信息。

 

整合OAuth2

关于 OAuth2 与 JWT 可以移步 浅谈常见的认证机制 。

基础依赖

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

 

基础配置

因为 OAuth2 涉及到资源服务器和授权服务器,所以除了配置 SpringSecurity ,还需要配置资源服务器和授权服务器。

1、SpringSecurity配置:

2、授权服务器配置:定义 app_id、app_secret,以及重定向地址,授权范围等

3、资源服务器配置:定义资源服务器资源权限角色配置。

 4、其他:userDetailsService 配置

自定义用户实体类 user ,权限属性全部设为 true。

资源服务器的资源Controller

 

授权码模式

在上面的授权服务器配置中,已将授权类型设为 授权码模式,所以直接使用上面的配置。

验证

1、获取授权码

访问 http://localhost:8080/oauth/authorize?response_type=code&client_id=client&redirect_uri=http://www.baidu.com&scope=all ,在登陆成功后(因为走的是上面 userDetailsService 的方法,所以用户名任意,密码123456就通过登陆),会重定向授权服务器配置中配置好的 http://www.baidu.com 。并且携带授权服务器返回的授权码 code。

2、获取授权令牌

接下来就可以再次访问 localhost:8080/oauth/token 携带授权码及其他数据来向授权服务器获取授权令牌。

 3、通过令牌访问资源服务器的资源,访问资源服务器上资源的 URL,并携带授权令牌。

 

密码模式

密码模式因为是通过密码直接获取授权令牌,所以不需要先获取授权码,同时需要设置自定义的 userDetailsService 实现类,以及 authenticationManager 组件

1、ServurityConfig里增加配置:

 2、授权服务器增加配置:

这样配置是同时支持授权码模式与密码模式

 

验证 

通过密码获取授权令牌

访问资源服务器的资源则和授权码模式验证一样。

 

整合 redis 将令牌存入 redis

1、引入依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>    

2、注册 redis 的 tokenStore 组件进容器

3、在授权服务器里注册 tokenStore

4、在配置文件里配置 redis 地址密码等。

 

使用 JWT 作为令牌

1、增加依赖

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>    

2、从容器中移除 redis 的 tokenStore 组件,同时想容器中加入 jwt 的 tokenStore 组件,并且配置 jwt 的转换器

3、注册进授权服务器

 

JWT 增加额外信息

1、增加 Jwt 附加信息组件并注册进容器

2、在授权服务器里配置 jwt 附加信息组件

3、验证,修改资源服务器资源返回的信息 

 

设置过期时间和刷新令牌

在授权服务器里增加配置:

 

在60s后token令牌(access_token)失效后,可以使用刷新令牌重新获取新的令牌,新的令牌过期时间也是60s。

因为密码模式不支持刷新令牌,所以通过授权码模式使用刷新令牌来获取新的令牌

通过刷新令牌获取令牌

 

整合SSO(单点登陆)

1、引入依赖

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>

 

2、新建一个模块,配置 SSO 访问授权服务器的地址

3、增加 SSO 模块的资源

4、主程序开启 OAuth2 自动配置

5、在授权服务器的配置增加配置

随后访问客户端资源 http://localhost:8081/user/getCurrentUser 就会先跳转到 http://localhost:8080/login ,也就是授权服务器进行授权验证,通过后经重定向回到 http://localhost:8081/login ,也就是客户端的登陆页面,并且携带授权服务器提供的jwt令牌,所以会自动解析通过验证,最后再访问客户端的资源

版权声明:本文为mengxinJ原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/mengxinJ/p/14897480.html