使用 OAuth 2.0 认证的的好处是显然易见的。你只须要用同一个帐号密码,就能在各个网站进行访问,而免去了在每一个网站都进行注册的繁琐过程。 本文将介绍 OAuth 2.0 的原理,并基于 Spring Security 和 GitHub 帐号,来演示 OAuth 2.0 的认证的过程。css
什么是 OAuth 2.0
OAuth 2.0 的规范能够参考 : RFC 6749html
OAuth 是一个开放标准,容许用户让第三方应用访问该用户在某一网站上存储的私密的资源(如照片,视频,联系人列表),而无需将用户名和密码提供给第三方应用。目前,OAuth 的最新版本为 2.0前端
OAuth 容许用户提供一个令牌,而不是用户名和密码来访问他们存放在特定服务提供者的数据。每个令牌受权一个特定的网站(例如,视频编辑网站)在特定的时段(例如,接下来的2小时内)内访问特定的资源(例如仅仅是某一相册中的视频)。这样,OAuth 容许用户受权第三方网站访问他们存储在另外的服务提供者上的信息,而不须要分享他们的访问许可或他们数据的全部内容。java
OAuth 2.0 的核心概念
OAuth 2.0 主要有4类角色:git
- resource owner:资源全部者,指终端的“用户”(user)
- resource server:资源服务器,即服务提供商存放受保护资源。访问这些资源,须要得到访问令牌(access token)。它与认证服务器,能够是同一台服务器,也能够是不一样的服务器。若是,咱们访问新浪博客网站,那么若是使用新浪博客的帐号来登陆新浪博客网站,那么新浪博客的资源和新浪博客的认证都是同一家,能够认为是同一个服务器。若是,咱们是新浪博客帐号去登陆了知乎,那么显然知乎的资源和新浪的认证不是一个服务器。
- client:客户端,表明向受保护资源进行资源请求的第三方应用程序。
- authorization server: 受权服务器, 在验证资源全部者并得到受权成功后,将发放访问令牌给客户端。
OAuth 2.0 的认证流程
认证流程以下:github
1web 2spring 3api 4安全 5 6 7 8 9 10 11 12 13 14 15 16 17 |
+--------+ +---------------+ | |--(A)- Authorization Request ->| Resource | | | | Owner | | |<-(B)-- Authorization Grant ---| | | | +---------------+ | | | | +---------------+ | |--(C)-- Authorization Grant -->| Authorization | | Client | | Server | | |<-(D)----- Access Token -------| | | | +---------------+ | | | | +---------------+ | |--(E)----- Access Token ------>| Resource | | | | Server | | |<-(F)--- Protected Resource ---| | +--------+ +---------------+ |
- (A)用户打开客户端之后,客户端请求资源全部者(用户)的受权。
- (B)用户赞成给予客户端受权。
- (C)客户端使用上一步得到的受权,向认证服务器申请访问令牌。
- (D)认证服务器对客户端进行认证之后,确认无误,赞成发放访问令牌。
- (E)客户端使用访问令牌,向资源服务器申请获取资源。
- (F)资源服务器确认令牌无误,赞成向客户端开放资源。
其中,用户受权有四种模式:
- 受权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
实践 OAuth 2.0
Talk is cheap!下面将演示代码。 本例子将经过 Gradle、Spring Boot、Spring Security、 Thymeleaf、等技术来实现一个client 以及 resource server,并 经过 GitHub来给咱们的应用受权。
依赖
本项目基于Gralde 来管理依赖,读者能够自行改为 Maven 的方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// 该依赖对于编译发行是必须的 compile( 'org.springframework.boot:spring-boot-starter-web' ) // 添加 Thymeleaf 的依赖 compile( 'org.springframework.boot:spring-boot-starter-thymeleaf' ) // 添加 Spring Security 依赖 compile( 'org.springframework.boot:spring-boot-starter-security' ) // 添加 Thymeleaf Spring Security 依赖,与 Thymeleaf 版本一致都是 3.x compile( 'org.thymeleaf.extras:thymeleaf-extras-springsecurity4:3.0.2.RELEASE' ) // 添加 Spring Security OAuth2 依赖 compile( 'org.springframework.security.oauth:spring-security-oauth2:2.1.0.RELEASE' ) // 该依赖对于编译测试是必须的,默认包含编译产品依赖和编译时依 testCompile( 'org.springframework.boot:spring-boot-starter-test' ) // 添加 Spring Security Test 依赖 testCompile( 'org.springframework.security:spring-security-test:4.2.2.RELEASE' ) |
配置
项目的核心配置以下:
1 2 3 4 5 6 7 8 |
github.client.clientId=ad2abbc19b6c5f0ed117 github.client.clientSecret=26db88a4dfc34cebaf196e68761c1294ac4ce265 github.client.accessTokenUri=https: //github.com/login/oauth/access_token github.client.userAuthorizationUri=https: //github.com/login/oauth/authorize github.client.clientAuthenticationScheme=form github.client.tokenName=oauth_token github.client.authenticationScheme=query github.resource.userInfoUri=https: //api.github.com/user |
包括了做为一个client 所须要大部分参数。其中 clientId 、 clientSecret 是在 GitHub 注册一个应用时生成的。若是读者不想注册应用,则能够直接用上面的配置便可。 若是要注册,则文章最后有注册流程。
项目安全的配置
安全配置上须要加上@EnableWebSecurity
、 @EnableOAuth2Client
注解,来启用 Web 安全认证机制,并代表这是一个OAuth 2.0 客户端 。 @EnableGlobalMethodSecurity
注明,项目采用了基于方法的安全设置 :
1 2 3 4 |
@EnableWebSecurity @EnableOAuth2Client // 启用 OAuth 2.0 客户端 @EnableGlobalMethodSecurity (prePostEnabled = true ) // 启用方法安全设置 public class SecurityConfig extends WebSecurityConfigurerAdapter { |
使用 Spring Security,咱们须要继承 org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter
并重写如下 configure 方法:
1 2 3 4 5 6 7 8 9 10 11 12 |
@Override protected void configure(HttpSecurity http) throws Exception { http.addFilterBefore(ssoFilter(), BasicAuthenticationFilter. class ) .antMatcher( "/**" ) .authorizeRequests() .antMatchers( "/" , "/index" , "/403" , "/css/**" , "/js/**" , "/fonts/**" ).permitAll() // 不设限制,都容许访问 .anyRequest() .authenticated() .and().logout().logoutSuccessUrl( "/" ).permitAll() .and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) ; } |
上面的配置是设置了一些过过滤策略,除了静态资源以及不须要受权的页面,咱们容许访问,其余的资源,都是须要受权访问。
其中,咱们也设置了一个过滤器 ssoFilter,用于在 BasicAuthenticationFilter 以前进行拦截。若是拦截道的是/login
,就是访问认证服务器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
private Filter ssoFilter() { OAuth2ClientAuthenticationProcessingFilter githubFilter = new OAuth2ClientAuthenticationProcessingFilter( "/login" ); OAuth2RestTemplate githubTemplate = new OAuth2RestTemplate(github(), oauth2ClientContext); githubFilter.setRestTemplate(githubTemplate); UserInfoTokenServices tokenServices = new UserInfoTokenServices(githubResource().getUserInfoUri(), github().getClientId()); tokenServices.setRestTemplate(githubTemplate); githubFilter.setTokenServices(tokenServices); return githubFilter; } @Bean public FilterRegistrationBean oauth2ClientFilterRegistration( OAuth2ClientContextFilter filter) { FilterRegistrationBean registration = new FilterRegistrationBean(); registration.setFilter(filter); registration.setOrder(- 100 ); return registration; } @Bean @ConfigurationProperties ( "github.client" ) public AuthorizationCodeResourceDetails github() { return new AuthorizationCodeResourceDetails(); } @Bean @ConfigurationProperties ( "github.resource" ) public ResourceServerProperties githubResource() { return new ResourceServerProperties(); } |
资源服务器
咱们写了两个控制器来提供相应的资源。
MainController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@Controller public class MainController { @GetMapping ( "/" ) public String root() { return "redirect:/index" ; } @GetMapping ( "/index" ) public String index(Principal principal, Model model) { if (principal == null ){ return "index" ; } System.out.println(principal.toString()); model.addAttribute( "principal" , principal); return "index" ; } @GetMapping ( "/403" ) public String accesssDenied() { return "403" ; } } |
在index 页面,将如认证成功,将会显示一些认证信息。
UserController.java 是用来模拟用户管理的相关资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
@RestController @RequestMapping ( "/" ) public class UserController { /** * 查询所用用户 * @return */ @GetMapping ( "/users" ) @PreAuthorize ( "hasAuthority('ROLE_USER')" ) // 指定角色权限才能操做方法 public ModelAndView list(Model model) { List<User> list = new ArrayList<>(); // 当前所在页面数据列表 list.add( new User( "waylau" , 29 )); list.add( new User( "老卫" , 30 )); model.addAttribute( "title" , "用户管理" ); model.addAttribute( "userList" , list); return new ModelAndView( "users/list" , "userModel" , model); } } |
前端页面
页面,我主要是采用 Thymeleaf 以及Bootstrap 来编写的。
首页用于现实用户的基本信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
< body > < div class = "container" > < div class = "mt-3" > < h2 >Hello Spring Security</ h2 > </ div > < div sec:authorize = "isAuthenticated()" th:if = "${principal}" th:object = "${principal}" > < p >已有用户登陆</ p > < p >登陆的用户为: < span sec:authentication = "name" ></ span ></ p > < p >用户权限为: < span th:text = "*{userAuthentication.authorities}" ></ span ></ p > < p >用户头像为: < img alt = "" class = "avatar width-full rounded-2" height = "230" th:src = "*{userAuthentication.details.avatar_url}" width = "230" ></ p > </ div > < div sec:authorize = "isAnonymous()" > < p >未有用户登陆</ p > </ div > </ div > </ body > |
用户管理界面显示用户的列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
< body > < div class = "container" > < div class = "mt-3" > < h2 th:text = "${userModel.title}" >Welcome to waylau.com</ h2 > </ div > < table class = "table table-hover" > < thead > < tr > < td >Age</ td > < td >Name</ td > < td sec:authorize = "hasRole('ADMIN')" >Operation</ td > </ tr > </ thead > < tbody > < tr th:if = "${userModel.userList.size()} eq 0" > < td colspan = "3" >没有用户信息!!</ td > </ tr > < tr th:each = "user : ${userModel.userList}" > < td th:text = "${user.age}" >11</ td > < td th:text = "${user.name}" >waylau</ a ></ td > < td sec:authorize = "hasRole('ADMIN')" > < div > 我是管理员 </ div > </ td > </ tr > </ tbody > </ table > </ body > |
运行效果
这个是没有受权访问首页:

当咱们点击登陆,会重定向到 GitHub,登陆界面并进行受权:

这个是受权后的首页:

受权后就可以进入用户管理界面:

注册GitHub 应用
若是须要注册,请看下面的流程,来生成 Client ID 和 Client Secret
访问https://github.com/settings/applications/new
注册应用,生成 客户端 id 和 密码。好比:
1 2 |
Client ID :ad2abbc19b6c5f0ed117 Client Secret :26db88a4dfc34cebaf196e68761c1294ac4ce265 |

客户端 id 和 密码写入程序配置便可。
源码