OAuth2.0及Spring实现

时间:2022-07-24
本文章向大家介绍OAuth2.0及Spring实现,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

一、什么是OAuth2.0

引用百度的解释:

OAuth2.0是OAuth协议的延续版本,但不向前兼容OAuth 1.0(即完全废止了OAuth1.0)。OAuth 2.0关注客户端开发者的简易性。要么通过组织在资源拥有者和HTTP服务商之间的被批准的交互动作代表用户,要么允许第三方应用代表用户获得访问的权限。同时为Web应用,桌面应用和手机,和起居室设备提供专门的认证流程。2012年10月,OAuth 2.0协议正式发布为RFC 6749 百度百科

说简单点就是用来认证的。

二、什么场景下需要使用OAuth2.0

一般来说需要开放能力给第三方的时候需要用到认证,注意是第三方,内部系统一般来说不用,因为内部我们一般有自己的用户中心或者登录中心,这些系统可以用来做认证,而为什么开放给第三方需要认证呢,主要有以下几方面的原因:

1、安全

内部调用用户中心,邮箱、手机号等信息一般都可以拿到,而开放给第三方的时候我们需要做好安全处理,以防第三方的原因造成个人数据泄露等问题。

2、重用

如果每个系统开放出去都需要自己实现一套认证流程,那系统的交互效率太低,所以会有一套统一的认证过程,来和第三方交互,一般来说这些在网关上用的比较多。

有兴趣可以看下如何集成qq登录,它的实现就是遵循OAuth2.0标准。

三、OAuth2.0流程及概念介绍

在介绍流程之前,先举1个场景,假如有个社交网站www.xxx.com想集成qq登录,用户在社交网站注册后会存放其个人数据,通过 www.xx.com/personal可以访问用户的头像等信息(实际url可能是网关的),我们也忽略参数等细节。

1、OAuth2.0角色

大概有以下几类角色:

A、Client

有的也叫第三方应用程序,指想集成资源访问的应用。

说的有点绕,拿上面的例子来说www.xxx.com 指的就是Client。

B、Resource Owner

资源所有者,一般指用户;

因为我们需要访问用户的邮箱、手机号信息,这些是属于用户的,需要征得用户同意,所以用户是资源所有者。

C、User Agent

用户代理,指浏览器。

D、Authorization server

认证服务器,即服务提供商专门用来处理认证的服务器。

E、Resource server

资源服务器,即服务提供商存放用户生成的资源的服务器。

回到上面的例子,假如我们要访问用户邮箱,即通过 www.xxx.com/personal这个接口获取,那资源服务器就是www.xxx.com所在服务器。

在部署上它可以与认证服务器在同一台服务器,也可以在不同的服务器。

2、OAuth2.0几种模式

A、授权码模式(Authorization code Grant)

贴上官方的图

步骤如下:

(1)用户访问Client,即www.xxx.com,后者将前者导向认证服务器,即graph.qq.com/oauth2.0/show;

(B)用户选择是否给予客户端授权;

(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。

(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。

(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)的请求。

B、简化模式(Implicit Grant)

同授权码模式相对,区别在于没有获取授权码的步骤。

C、密码模式(Resource Owner Password Credentials Grant)

(1)用户向客户端(www.xxx.com)提供用户名和密码。

(2)客户端(www.xxx.com)将用户名和密码发给认证服务器(open.qq.com),向后者请求令牌。

(3)认证服务器确认无误后,向客户端提供访问令牌。

D、客户端模式(client credentials)

这是一种最简单的模式,只要client请求,即在www.xxx.com后台任务发起请求,我们就将AccessToken发送给它。

四、Spring实现

因为篇幅的原因,不准备写太多代码,我们以一个小的demo来跑下流程。

在idea中创建一个springbot的项目,注意选择spring web和spring security,

新增认证服务器代码

@Configuration
@EnableAuthorizationServer
public class ConfigAdapter extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private DataSource myDataSource;

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        super.configure(security);
    }

    @Bean
    public TokenStore tokenStore() {
        return new InMemoryTokenStore();
    }

    @Bean
    public PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }

    /**
     * 数据源
     * @return
     */
    public DataSource dataSource(){
        return myDataSource;
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client-id")
                .secret("client-secret")
                .scopes("read", "write")
                .authorizedGrantTypes("password", "refresh_token", "code", "authorization_code")  //对应response_type是否有权限
                .redirectUris("http://www.baidu.com")
                .authorities("user:view");
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager)
                .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST);
    }

}

再加入认证逻辑,这里先写死,简单点:

public class MyAuthenticationManager implements AuthenticationManager {

    /*
        授权过程
     */
    public Authentication authenticate(Authentication authentication) throws AuthenticationException{
        String userName, password;

        userName = (String)authentication.getPrincipal();
        password = (String)authentication.getCredentials();

        if ((userName == "edward") && (password == "123")){
            Authentication res = new UsernamePasswordAuthenticationToken(authentication.getPrincipal()
                    , authentication.getPrincipal());
            return res;
        }

        return null;
    }
}

再配置用户信息,也是先写死:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    MyAuthenticationProvider myAuthenticationProvider() {
        return new MyAuthenticationProvider();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("edward").password("123").authorities("USER")
                .and()
                .withUser("user_2").password("123456").authorities("USER");
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception{
        //http.regexMatcher("/image/.+").authorizeRequests().anyRequest().authenticated();
        http.authorizeRequests().antMatchers("/post/**", "/oauth/**", "/login/**").permitAll()
                .anyRequest().authenticated()
                .and().formLogin().permitAll()
        ;
    }

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

    @Bean
    @Override
    protected UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("edward").password("123").authorities("USER").build());
        manager.createUser(User.withUsername("user_2").password("123456").authorities("USER").build());
        return manager;
    }

}

再新加一个控制器, 这个表示我们的要保护的资源

@RestController
@RequestMapping("/user")
public class UserController {

    @GetMapping("hello")
    public String hello(){
        return "hello user";
    }
}

然后在浏览器访问授权码模式的几个URL:

1、获取token

http://localhost:8080/oauth/authorize?response_type=code&client_id=client-id&redirect_uri=http://www.baidu.com

2、根据code获取token

http://localhost:8080/oauth/token?grant_type=authorization_code&code=Dx5tnU&client_id=client-id&client_secret=client-secret&redirect_uri=http://www.baidu.com

这一步会跳转到www.baidu.com,并且返回一个accessToken

3、根据accessToken访问资源

http://localhost:8080/user/hello?accessToken=123

中间需要登录,输入上面定义的账号和密码

edward

123