SpringBoot + Spring Security OAuth2 的简单实现

JAVA herman 1880浏览
公告:“业余草”微信公众号提供免费CSDN下载服务(只下Java资源),关注业余草微信公众号,添加作者微信:xttblog,发送下载链接帮助你免费下载!
本博客日IP超过1800,PV 2600 左右,急需赞助商。
极客时间所有课程通过我的二维码购买后返现24元微信红包,请加博主新的微信号:xttblog,之前的微信号好友位已满,备注:返现
所有面试题(java、前端、数据库、springboot等)一网打尽,请关注文末小程序
视频教程免费领

随着 OAuth2(开放授权)的火热,越来越多的网站选择使用 OAuth2 技术获得用户。这样做不需要用户过多的注册账号,维护账号以及担心用户个人信息泄露等问题。所以说与 OAuth2 相关的关键词都火热了起来。比如:OAuth 2 c#、oauth 2.0 阮一峰、spring OAuth2、oauth2使用、oauth2与jwt、oauth2.0 认证、oauth2 cookie、oauth2 sso、ajax token认证实例、oauth2流程、oauth2 jwt、oauth2 token、oauth2 spring、oauth2 java、oauth2 server、oauth2 code、oauth2 grant_type、oauth2维基、oauth2 scope等。本文我们来做一个 OAuth2 的简单实现案例。

网上有很多关于 OAuth2 的相关开源实现。我们今天借助 spring-security-oauth2 实现 Authorization Code Grant 授权模式。 

OAuth2 的认证流程

上图的这个 OAuth2 授权认证过程有几个重要的 URL,也是 spring-security-oauth2 中的核心 URL。具体列举如下:

  • /oauth/authorize : 授权AuthorizationEndpoint
  • /oauth/token : 令牌TokenEndpoint
  • /oauth/check_token : 令牌校验CheckTokenEndpoint
  • /oauth/confirm_access : 授权页面WhitelabelApprovalEndpoint
  • /oauth/error : 错误页面WhitelabelErrorEndpoint

后面我们所有的例子,基本都是围绕这 5 个 URL 展开。

新建一个 xttblog-oauth2-server 工程。pom.xml 文件中的配置如下:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.security.oauth</groupId>
        <artifactId>spring-security-oauth2</artifactId>
        <version>${oauth.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
</dependencies>

@EnableAuthorizationServer 注解相关的使用如下:

public class Config {  
    public static final String OAUTH_CLIENT_ID = "oauth_client";  
    public static final String OAUTH_CLIENT_SECRET = "oauth_client_secret";  
    public static final String RESOURCE_ID = "my_resource_id";  
    public static final String[] SCOPES = { "read", "write" };  
    @Configuration  
    @EnableAuthorizationServer  
    static class OAuthAuthorizationConfig extends AuthorizationServerConfigurerAdapter {  
        @Override  
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {  
            clients.inMemory()  
                .withClient(OAUTH_CLIENT_ID)  
                .secret(OAUTH_CLIENT_SECRET)  
                .resourceIds(RESOURCE_ID)  
                .scopes(SCOPES)  
                .authorities("ROLE_USER")  
                .authorizedGrantTypes("authorization_code", "refresh_token")  
                .redirectUris("http://default-oauth-callback.com")  
                .accessTokenValiditySeconds(60*30) // 30min  
                .refreshTokenValiditySeconds(60*60*24); // 24h  
        }  
    }  
    @Configuration  
    @EnableResourceServer  
    static class OAuthResourceConfig extends ResourceServerConfigurerAdapter {  
        @Override  
        public void configure(ResourceServerSecurityConfigurer resources) throws Exception {  
            resources.resourceId(RESOURCE_ID);  
        }  
        @Override  
        public void configure(HttpSecurity http) throws Exception {  
            http.authorizeRequests()  
                .antMatchers(HttpMethod.GET, "/api/**").access("#oauth2.hasScope('read')")  
                .antMatchers(HttpMethod.POST, "/api/**").access("#oauth2.hasScope('write')");  
        }  
    }  
    @Configuration  
    @EnableWebSecurity  
    static class SecurityConfig extends WebSecurityConfigurerAdapter {  
        @Override  
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {  
            auth.inMemoryAuthentication()  
                .withUser("user").password("123").roles("USER")  
                .and()  
                .withUser("admin").password("123").roles("ADMIN");  
        }  
        @Override  
        protected void configure(HttpSecurity http) throws Exception {  
            http.csrf().disable();  
            http.authorizeRequests()  
                .antMatchers("/oauth/authorize").authenticated()  
                .and()  
                .httpBasic().realmName("OAuth Server");  
        }  
    }  
}  

Controller 类,测试授权后才能访问。

@RestController  
public class Controller {  
    @GetMapping("/api/get")  
    public String get() {  
        return "Hello World!";  
    }  
    @PostMapping("/api/post")  
    public String post() {  
        return "POST process has finished.";  
    }  
    @GetMapping("/api/user")  
    public Object get(HttpServletRequest req) {  
        SecurityContextImpl sci = (SecurityContextImpl) 
        req.getSession().getAttribute("SPRING_SECURITY_CONTEXT");  
        if (sci != null) {  
            Authentication authentication = sci.getAuthentication();  
            if (authentication != null) {  
                return authentication.getPrincipal();  
            }  
        }  
        return "none";  
    }  
}  

SpringBoot 启动类代码如下:

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

上面的 oauth_client 和 oauth_client_secret 是两个 Base64 的加密字符串。对接过微信的应该都了解。

然后我们启动程序,访问 /api/get 。请求就会被拦截到 /oauth/authorize 进行 HTTP Basic 认证,因为我们前面在 WebSecurityConfigurerAdapter 中开启了 HTTP Basic 认证。

OAuth2 HTTP Basic 认证

输入正确用户名密码(user/123)后显示授权页:

OAuth 默认的授权页

上面这两个过程,我们都可以进行定制。URL 中的 code 参数值即为授权码,该值是一个 6 位数的随机数。由 RandomValueStringGenerator.generate() 生成。

一般的,授权码都有一个过期时间,这里我们后面再说。令牌 access_token 是一个 UUID,默认实现是 DefaultTokenServices.createAccessToken() 。

刷新令牌,使用的是 /oauth/token?grant_type=refresh_token&refresh_token=5b319fed-5600-4ea2-8c4f-61f6e3ea6e41 这个 URL,refresh_token 是一个旧的令牌。调用这个 URL 后,久的令牌会失效,另外会返回一个新的令牌。

参考资料

业余草公众号

最后,欢迎关注我的个人微信公众号:业余草(yyucao)!可加QQ1群:135430763(2000人群已满),QQ2群:454796847(已满),QQ3群:187424846(已满)。QQ群进群密码:xttblog,想加微信群的朋友,之前的微信号好友已满,请加博主新的微信号:xttblog,备注:“xttblog”,添加博主微信拉你进群。备注错误不会同意好友申请。再次感谢您的关注!后续有精彩内容会第一时间发给您!原创文章投稿请发送至532009913@qq.com邮箱。商务合作可添加助理微信进行沟通!

本文原文出处:业余草: » SpringBoot + Spring Security OAuth2 的简单实现