Skip to content

使用Crust添加安全认证功能

Yizzuide edited this page Dec 30, 2019 · 8 revisions

依赖版本

1.14.0+

模块说明

Crust模块基于Spring Security和JWT,可用于在项目中快速接入登录认证功能,提供以下功能:

  1. 单应用session会话登录认证(如后台管理)、微服务无状态的Token登录。
  2. 支持BCrypt加密方式、自定义Salt加密。
  3. 内建支持Light模块缓存获取当前登录用户信息,在session会话方式下默认有超级缓存,在无状态的Token可选开启缓存级别(一级缓存内存池丢弃策略,二级缓存Redis过期方案)。
  4. 使用门面API模式访问类,让使用更简单。

安装

当前文档使用的版本为2.0.6,使用以下坐标安装:

<dependency>
  <groupId>com.github.yizzuide</groupId>
  <artifactId>milkomeda-spring-boot-starter</artifactId>
  <version>2.0.6</version>
</dependency>

<!-- Spring Security -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!-- Token方式开启二级缓存下添加需要Redis -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

使用前的配置

1. 启用模块

@EnableCrust
@SpringBootApplication
public class IceServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(IceServerApplication.class, args);
    }
}

2. 用户实体类实现CrustEntity接口

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements CrustEntity {
    private static final long serialVersionUID = -9190491929257431915L;
    private String id;
    private String username;
    @JsonIgnore
    private String password;
    // 可选字段(自定义salt加密方式时添加)
    @JsonIgnore
    private String salt;

    // 把当前实体`id`字段适配到接口里`uid`的getter
    @Override
    public String getUID() {
        return id;
    }
}

2. 创建数据源提供服务UserDetailsService

public class UserDetailsService extends CrustUserDetailsService {
    // Token登录方式下开启缓存需要实现的方法(根据解析token的uid查找实体),session登录方式不需要实现
    @Override
    protected Serializable findEntityById(String uid) {
        // 实际情况下通过Dao查询
        // 这里使用BCryptPasswordEncoder模拟实际表中被加密过的字段
        return new User("1000", "yiz", new BCryptPasswordEncoder().encode("123456"), null);
    }

    @Override
    protected CrustEntity findEntityByUsername(String username) {
        // 实际情况下通过Dao查询,未找到,直接返回null

        // 这里使用BCryptPasswordEncoder模拟实际表中被加密过的字段
        // 模拟自定义salt字段方式,salt为111222
//        return new User("1000", username, new PasswordEncoder("111222").encode("123456"), "111222");
        // 模拟BCrypt方式
        return new User("1000", username, new BCryptPasswordEncoder().encode("123456"), null);
    }

    @Override
    protected CrustPerm findPermissionsById(String uid, String username) {
        // 实际情况下通过Dao查询

        CrustPerm crustPerm = new CrustPerm();
        crustPerm.setPermNames(Collections.singletonList("ROLE_USER"));
        return crustPerm;
    }
}

4. Security配置

@Configuration
public class WebSecurityConfig extends CrustConfigurerAdapter {

    @Bean
    public UserDetailsService userDetailsService() {
        return new UserDetailsService();
    }

    // 配置UserDetailsService
    @Override
    protected void configureProvider(DaoAuthenticationProvider provider) {
        provider.setUserDetailsService(userDetailsService());
    }

    // 配置需要被忽略的URL(默认`/login`已添加到允许访问)
    @Override
    protected void additionalConfigure(ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry urlRegistry, HttpSecurity http) throws Exception {
        urlRegistry.antMatchers("/test/**").permitAll()
    }
}

无状态Token登录配置方案

配置分为采用对称加密方式和非对称加密方式。对称加密方式比较简单,只需要一个key就能加密与解密,推荐使用;非对称加密(常用RSA)需要使用两个key,私钥用于加密,公钥用于解密。

1.1. 采用对称加密方式生成token的配置

milkomeda:
  crust:
    # 自定义对称密钥,crust_secure_key是对称密钥值的例子,可以改其它字符串
    secure-key: crust_secure_key
    # 自定义请求token头字段名,默认为'token'(token值可有`Bearer `前辍,内部会自动判断)
    token-name: Authorization
    # token过期时间(分钟)
    expire: 60
    # 不使用BCrypt(如果用户表有salt字段的情况)
    #use-bcrypt: false
    # 禁用登录用户信息缓存
    #enable-cache: false
    # enable-cache为true情况下,禁用Redis缓存(只使用内存池丢弃策略缓存)
    #enable-cache-l2: false

  # 缓存开启时,light模块的配置
  light:
    # 二级缓存一天后过期
    l2-expire: 86400
    # 使用时间线丢弃策略
    strategy: timeline

1.2. 采用非对称加密方式生成token的配置

milkomeda:
  crust:
    # 开启非对称方式
    use-rsa: true
    # RSA私钥
    pri-key: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAJr1tdb8ZOqx4UtbEF+cQJvFo1wxMAYOiNKebC1Rw932bCd69GC44gNs7FtKwjAO031vDejXpXk35juK6W4QeytaGuEL1FDHosD2Hye5MTpzUdKy2CrvL4NI+9mtui7nNmcNuA/jkzYtPYxd3cdoCmr45y+DmCnXVaEpFrgCBd6tAgMBAAECgYBuFRGZ6XFTnQw8uTN3iIwJXSzBCJxiIR8n6K1WwJhRbYbFwT4sHAtLfaym6gPrmgy6NhN+jvuZkpF3SSatLv4f3vwu3ToZcmi6A0LlVzFT7cMHBzMP/Ev09aa0N/j9+ykPlJH06ehkvwz/504GEDwLt2791MxWqtZJjuDNWloWQQJBAOaQ+jgUmjIkKX09/x0a8P13JezBP14UV5cZLvoRWW8XzTkfZx/rpC7irpijvcwBhi45kIg8JrIngYm5/QSkLDECQQCsDakrLtIJLenTSHmFi2KfI2EHYT0deFrK92+VDY15iE7gBQBvbiZiAKwjV33gcrtS6JTZWtxKeOAUUPTiUgc9AkEA1Gb+e6dPHZ3+sqfoWxG0rGuU/nRQQgUPY90JT8mn0BXnMxZg1CEqkR62pVtCv6svx2m0Yiy3oSuPxCcYlawAIQJAW5lORjJAGij6gsTkBagmkkjYoIAxdF4eIE7JdhZoCpr6OyQOjkSbZLOs8Yfj+Tm75zDyBiHshC2ERuyu40r+lQJBAJ+SImDYm9NLqo6hq1+FTI1apKyq8rsuQYL8IRsYAHmOGCNmHN1b/AFitHaptZXYOtiZyuEP8xP86Np8vrTUKSg=
    # RSA公钥
    pub-key: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCa9bXW/GTqseFLWxBfnECbxaNcMTAGDojSnmwtUcPd9mwnevRguOIDbOxbSsIwDtN9bw3o16V5N+Y7iuluEHsrWhrhC9RQx6LA9h8nuTE6c1HSstgq7y+DSPvZrbou5zZnDbgP45M2LT2MXd3HaApq+Ocvg5gp11WhKRa4AgXerQIDAQAB    
    # 自定义请求token头字段名,默认为'token'(token值可有`Bearer `前辍,内部会自动判断)
    token-name: Authorization
    # token过期时间(分钟)
    expire: 60
    # 不使用BCrypt(如果用户表有salt字段的情况)
    #use-bcrypt: false
    # 禁用登录用户信息缓存
    #enable-cache: false
    # enable-cache为true情况下,禁用Redis缓存(只使用内存池丢弃策略缓存)
    #enable-cache-l2: false

  # 缓存开启时,light模块的配置
  light:
    # 二级缓存一天后过期
    l2-expire: 86400
    # 使用时间线丢弃策略
    strategy: timeline

单应用Session登录配置方案

# 配置session过期时间,默认30m(30分钟)
server:
  servlet:
    session:
      timeout: 120m

milkomeda:
  crust:
    # 使用session登录方式
    stateless: false
    # 不使用BCrypt(如果用户表有salt字段的情况)
    #use-bcrypt: false
    # 自定义登录页面 
    login-url: /login.html
    # 自定义登出处理(登出成功后自动跳转到login-url配置的页面)
    logout-url: /logout

开始使用

下面的使用方式同时适用于Session方式登录和无状态Token认证方式,模块内部根据上面的配置自动区分。

1. 登录

@RestController
public class LoginController {

    @PostMapping("login")
    public CrustUserInfo<User> login(String username, String password) {
        return CrustContext.get().login(username, password, User.class);
    }
}

2. 访问需要授权的业务方法

@Slf4j
@RestController
@RequestMapping("case")
public class CaseController {

    @GetMapping("info")
//    @PreAuthorize("hasAuthority('ROLE_USER')")
    // 和上面等同
    @PreAuthorize("hasRole('USER')")
    public Map<String, Object> info() {
       // 获取当前登录用户信息
        CrustUserInfo<User> userInfo = CrustContext.getUserInfo(User.class);
        log.info("userInfo: {}", userInfo);
        Map<String, Object> data = new HashMap<>();
        data.put("id", "12345667009874");
        data.put("name", "case-01");
        // 同一请求线程的多次调用,返回同一个对象
        CrustUserInfo<User> userInfo2 = CrustContext.getUserInfo(User.class);
        log.info("比较两个对象:{}", userInfo == userInfo2);
        return data;
    }
}

高级使用

Token方式下的刷新Token

1. 手动刷新Token

@RestController
public class LoginController {

    @GetMapping("refresh")
    public CrustUserInfo<User> refresh() {
        CrustUserInfo<User> userInfo = CrustContext.get().getUserInfo(User.class);
        String token = CrustContext.get().refreshToken();
        userInfo.setToken(token);
        return userInfo;
    }
}

2. 自动刷新Token

注意:自动刷新Token机制,不是每隔一段时间一定会刷新Token,内部会根据当前发行的Token时间有效期自动选择是否刷新,如果当前访问的Token已经过期了,还是走认证失败了的流程。

判别式:token过期时间 - token刷新间隔秒数 < 当前时间

2.1 配置
milkomeda:
  crust:
    # 自动刷新Token(默认是开启的,不需要时设置为false)
    enableAutoRefreshToken: true
    # 刷新间隔(默认5分钟),在这个访问间隔内Token有效情况下可以生成新的Token
    refreshTokenInterval: 5
    # 设置token刷新响应字段(默认为Authorization)
    refreshTokenName: Authorization
2.2 获取刷新的Token
# 前端通过获取响应头
Authorization: token的值