第十九天、OAuth2的使用

345 阅读4分钟

1. OAuth2 是什么?

OAuth2是目前最流行的授权框架,用来授权第三方应用,获取用户数据。

OAuth2的授权模式

  • 授权码模式。最完整和严谨的授权模式,第三方平台登录都是使用的此模式。安全性最高
  • 简化模式。省略授权码阶段,客户端是纯静态页面采用此模式。安全性较高
  • 密码模式。把用户名密码告诉客户端,对客户端高度信任,比如客户端和认证服务是同一个公司。安全性一般
  • 客户端模式。直接以客户端名义申请令牌,很少用。安全性最差

2. 为什么要用OAuth2

在最早的单体架构中,我们使用的是cookie session机制来进行身份的校验的,这种方式既不安全,也存在跨域的问题 image.png
随着业务的发展,架构由单体应用发展到了分布式的架构,我们需要一种新的安全的方式来进行身份的校验

  • 使用session
    但是使用session会存在分布式应用下session的判断问题,此时session我们可以使用redis来存储。
  • 基于token
    由一台授权服务器进行授权,客户端获取到token后,调用资源服务器的时候,携带上token进行访问即可。

Cookie session 和 token的区别

  • cookie是不能跨域的,前后端分离分布式架构实现多系统SSO非常困难
  • 移动端应用没有cookie,所以对移动端的支持不好
  • token基于header传递,部分解决了CSRF攻击
  • token要比sessionId大,客户端存储在Local Storage中,可以直接被JS读取

3. OAuth2的使用

使用OAuth2的时候,需要一个授权服务器-用来办法和验证令牌,其他的访问段称之为资源服务器-需要令牌才能够访问的服务。

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>

2. 授权服务器配置

需要配置一个OAuths的配置类,需要继承AuthorizationServerConfigurerAdapter 代码如下:

@Configuration
@EnableAuthorizationServer //开启授权配置服务类
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;
    // 客户端的id
    private static final String CLIENT_ID = "客户端的id";
    // 是否对信息进行加密 {noop}secret就是不需要
    private static final String SECRET_CHAR_SEQUENCE = "{noop}secret";
    // 范围。是全部还是只读,只写
    private static final String ALL = "all";
    // 有效期
    private static final int ACCESS_TOKEN_VALIDITY_SECONDS = 30*60;
    // 密码模式授权模式
    private static final String GRANT_TYPE_PASSWORD = "password";
    //授权码模式
    private static final String AUTHORIZATION_CODE = "authorization_code";
    //简化授权模式
    private static final String IMPLICIT = "implicit";
    //客户端模式
    private static final String CLIENT_CREDENTIALS="client_credentials";
    // 客户端配置信息
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients
                .inMemory()
                .withClient(CLIENT_ID)
                .secret(SECRET_CHAR_SEQUENCE)
                .autoApprove(false)
                .redirectUris("重定向uri") //重定向uri
                .scopes(ALL)
                .accessTokenValiditySeconds(ACCESS_TOKEN_VALIDITY_SECONDS)
                .authorizedGrantTypes(AUTHORIZATION_CODE, IMPLICIT, GRANT_TYPE_PASSWORD, CLIENT_CREDENTIALS);
    }
    // 令牌的存储方式
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.authenticationManager(authenticationManager).tokenStore(memoryTokenStore());
    }

    /**
     * 认证服务器的安全配置
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                //  开启/oauth/check_token验证端口认证权限访问,checkTokenAccess("isAuthenticated()")设置授权访问
                .checkTokenAccess("permitAll()")
                //允许表单认证
                .allowFormAuthenticationForClients();
    }
    @Bean
    public TokenStore memoryTokenStore() {
        return new InMemoryTokenStore();
    }

}

配置Spring Security,需要继承WebSecurityConfigurerAdapter类,实现Spring Security的权限配置

@Configuration
@EnableWebSecurity
@Order(1)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    // 设置用户角色数据内存储存,这里可以修改为从数据库获取对应的用户信息并进行校验
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {    //auth.inMemoryAuthentication()
        auth.inMemoryAuthentication()
                .withUser("用户")
                .password("{noop}密码") //使用springsecurity5,需要加上{noop}指定使用NoOpPasswordEncoder给DelegatingPasswordEncoder去校验密码
                .roles("角色");
    }

  // 需要放行的资源
    @Override
    public void configure(WebSecurity web) throws Exception {
        //解决静态资源被拦截的问题
//        web.ignoring().antMatchers("/asserts/**");
    }

  // 授权的全局资源
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .formLogin().permitAll()
                .and().logout().logoutUrl("/logout").logoutSuccessUrl("/")
                .and().authorizeRequests().antMatchers("/oauth/**", "/login/**", "/logout/**", "/api/**").permitAll()
                .anyRequest().authenticated()
                // 关闭跨域保护;
                .and().csrf().disable();
    }

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

}

配置好以后就可以使用http://localhost:8888/oauth/authorize?client_id=cms&client_secret=secret&response_type=code来测试一下看是否可以获取到token,这个的respone_type=code说明它是验证码模式

3. 资源服务器的配置

1. 需要引入两个依赖,和授权服务器一样
2. 配置类配置
  • Oauth2ResourceServerConfiguration继承ResourceServerConfigurerAdapter 代码如下
@Configuration
@EnableResourceServer
public class Oauth2ResourceServerConfiguration extends
    ResourceServerConfigurerAdapter {
// 进行token校验的地址
  private static final String CHECK_TOKEN_URL = "http://localhost:8888/oauth/check_token";

  @Override
  public void configure(ResourceServerSecurityConfigurer resources) {

    RemoteTokenServices tokenService = new RemoteTokenServices();

    tokenService.setCheckTokenEndpointUrl(CHECK_TOKEN_URL);
    tokenService.setClientId("授权的客户端id");
    tokenService.setClientSecret("授权的客户端密码");

    resources.tokenServices(tokenService);
  }

}
  • SecurityConfiguration继承WebSecurityConfigurerAdapter 代码如下:
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

  @Override
  protected void configure(HttpSecurity http) throws Exception {
   // 可以在这里自定义需要放行的接口
    http.authorizeRequests().antMatchers("/**").authenticated();
    // 禁用CSRF
    http.csrf().disable();
  }
}

接下来去直接访问资源服务器就会发现已经进行了权限校验了,此时可以携带token去访问,是可以访问成功的。