Featured image of post Spring Cloud OAuth2 从零开始实现用户认证和单点登录

Spring Cloud OAuth2 从零开始实现用户认证和单点登录

Spring Cloud OAuth2 实现用户认证和单点登录(password 模式)

# Spring Cloud OAuth2 从零开始实现用户认证和单点登录

# OAuth2 是什么

OAuth2 其实是一个关于授权的网络标准,它制定了设计思路和运行流程,利用这个标准我们其实是可以自己实现 OAuth2 的认证过程的。 spring-cloud-starter-oauth2 是 Spring Cloud 按照 OAuth2 的标准并结合 spring-security 封装好的一个具体实现。

OAuth 2 有四种授权模式,分别是授权码模式(authorization code)、简化模式(implicit)、密码模式(resource owner password credentials)、客户端模式(client credentials),具体 OAuth2 是什么,可以参考这篇文章(http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html )。

# OAuth2 的使用场景

  • 典型的 OAuth2 使用场景:微信登录、QQ 登录、微博登录、Google 帐号登录、Github 帐号登录等。第一次使用就无需注册,直接通过第三方平台授权登录即可,大大提高了使用效率。此外,服务不需要存储用户的密码,只需要存储认证平台返回的唯一 ID 和用户信息即可。
  • 不使用 OAuth2 的场景:用户需要先完成注册,然后用注册号的帐号密码或者用手机验证码登录。

# OAuth2 实现统一认证功能

# 创建并配置认证服务端 auth-server

认证服务端负责验证帐号、密码、存储 Token、检查 Token、刷新 Token 等。

# 1、引入需要的 Maven 包

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

# 2、配置 bootstrap.yml 和 Nacos 配置

认证服务器采用 Nacos Config 方案,将配置放在 Nacos 注册中心上

  • bootstrap.yml 配置
spring:
  application:
    name: auth-server
  cloud:
    nacos:
      config:
        prefix: auth-server-config
        server-addr: xxxx
        file-extension: yaml
        group: refactored-spring-cloud
  • auth-server-config 配置
server:
  port: 18003

spring:
  datasource:
    url: jdbc:mysql://xxxx:3306/spring?useUnicode=true&&characterEncoding=UTF-8&&serverTimezone=Asia/Shanghai
    username: xxxx
    password: xxxx
  jpa:
    show-sql: true
    generate-ddl: true
    database-platform: org.hibernate.dialect.MYSQL5InnoDBDialect
    database: mysql
  application:
    name: auth-server
  cloud:
    nacos:
      discovery:
        server-addr: xxxx:8848
        group: refactored-spring-cloud
      inetutils:
        ignored-interfaces: eth.*
        preferred-networks: xxxx
  redis:
    host: xxxx
    port: 6379

management:
  endpoint:
    health:
      enabled: true

dubbo:
  protocol:
    name: dubbo
    port: -1
  registry:
    address: spring-cloud://xxxx
  consumer:
    timeout: 3000

# 3、配置 Spring Security

public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    /**
     * 开放所有接口
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/**")
                .permitAll();
    }
}
  • PasswordEncoder:采用 BCrypt 加密算法
  • AuthenticationManager:OAuth2 密码模式必须制定的授权管理,用默认的即可
  • configure:配置拦截器,使用通配符开放所有接口访问权限

# 4、实现 UserDetailsService

@Slf4j
@Commponent(value = "kiteUserDetailService")
public class KiteUserDetailService implements UserDetailService {
    @DubboReference
    IUserService service;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("username is: " + username);
        // 查询用户
        if (user == null) {
            throw new UsernameNotFoundException("The user is not found");
        } else {
            // 查询角色
            List<SysRole> roles = user.getRoles();
            List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            for (SysRole role : roles) {
                authorities.add(new SimpleGrantedAuthority(role.getRoleName()));
            }
            // 查询密码
            String password = user.getPassword();
            return new User(username, password, authorities);
        }
    }
}
  • loadUserByUsername:首先利用用户微服务接口通过 username 查询用户、角色以及密码,然后返回org.springframework.security.core.userdetails.User 即可。

# 5、配置 OAuth2

@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
    @Autowired
    public PasswordEncoder passwordEncoder;

    @Autowired
    public UserDetailsService kiteUserDetailsService;

    @Autowired
    private TokenStore jwtTokenStore;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    @Autowired
    private DataSource dataSource;

    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // Redis token 方式
        endpoints.authenticaionManager(authenticationManager)
                .userDetailsService(kiteUserDetailsService)
                .accessTokenConverter(jwtAccessTokenConverter)
                .tokenStore(jwtTokenStore);
    }
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        JdbcClientDetailsServiceBuilder builder = clients.jdbc(dataSource);
        builder.passwordEncoder(passwordEncoder);
    }
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFromAuthenticationForClients();
        security.checkTokenAccess("isAuthenticated");
        security.tokenKeyAccess("isAuthenticated");
    }
}

有三个 configure 方法的重写

  • AuthorizationServerEndpointConfigurer 参数的重写

    • authenticationManager:用于支持 password 模式
    • userDetailsService:设置用户验证服务
    • tokenStore:制定 token 的存储方式
    • accessTokenConverter:开启 json web token 模式
  • ClientDetailsServiceConfigure 参数的重写:采用数据库配置的方式,预先定义好 oauth2_client_details 表,如下:

    • 参数说明:

      • clientId、client_secret:这两个参数对应请求端定义的 cleint-id 和 client-secret
      • authorized_grant_types:包括 authorization_code(授权码模式)、password(密码模式)、implicit(隐式授权类型)、client_credentials、refresh_token 这五种中的一种或多种。
      • access_token_validity:token 的有效期
      • scopes:用来限定客户端访问的权限,只有在 scopes 定义内的,才可以正常换取 token。
create table oauth_client_details (
    client_id VARCHAR(256) PRIMARY KEY,
    resource_ids VARCHAR(256),
    client_secret VARCHAR(256),
    scope VARCHAR(256),
    authorized_grant_types VARCHAR(256),
    web_server_redirect_uri VARCHAR(256),
    authorities VARCHAR(256),
    access_token_validity INTEGER,
    refresh_token_validity INTEGER,
    additional_information VARCHAR(4096),
    autoapprove VARCHAR(256)
);
  • AuthorizationServerSecurityConfigurer 参数的重写:限制客户端访问认证接口的权限

    • allowFormAuthenticationForClients():允许客户端访问 OAuth2 授权接口,否则返回 401
    • checkTokenAccess :允许已授权用户访问 checkToken 接口。
    • tokenKeyAccess:允许已授权用户访问获取 token 接口。

# 6、配置 JWTTokenStore

@Configuration
public class JWTTokenStore {
    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }
    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        accessTokenConverter.setSigningKey("dev");
        return accessTokenConverter;
    }
}

# 7、启动 auth-server

现在已经可以访问 OAuth2 相关的 Restful 接口:

  • POST /oauth/authorize 授权码模式认证授权接口
  • GET/POST /oauth/token 获取 token 的接口
  • POST /oauth/check_token 检查 token 合法性接口
本博客已稳定运行
总访客数: Loading
总访问量: Loading
发表了 73 篇文章 · 总计 323.75k

使用 Hugo 构建
主题 StackJimmy 设计
基于 v3.27.0 分支版本修改