赞
踩
具体关于oauth2,请参考https://blog.csdn.net/liaomin416100569/article/details/78871969
简要介绍一下使用到的概念和术语
(1) Third-party application:第三方应用程序,本文中又称"客户端"(client)。
(2)HTTP service:HTTP服务提供商,本文中简称"服务提供商"。
(3)Resource Owner:资源所有者,本文中又称"用户"(user)。
(4)User Agent:用户代理,本文中就是指浏览器。
(5)Authorization server:认证服务器,即服务提供商专门用来处理认证的服务器。
(6)Resource server:资源服务器,即服务提供商存放用户生成的资源的服务器。它与认证服务器,可以是同一台服务器,也可以是不同的服务器。
OAuth 2.0的运行流程如下图
过程
(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。
客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了四种授权方式。
常用的是授权码模式
它的步骤如下:
(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
下面是上面这些步骤所需要的参数。
A步骤中,客户端申请认证的URI,包含以下参数:
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
C步骤中,服务器回应客户端的URI,包含以下参数:
HTTP/1.1 302 Found
Location: https://client.example.com/cb?code=SplxlOBeZQQYbYS6WxSbIA
&state=xyz
D步骤中,客户端向认证服务器申请令牌的HTTP请求,包含以下参数:
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb
E步骤中,认证服务器发送的HTTP回复,包含以下参数:
access_token:表示访问令牌,必选项。
token_type:表示令牌类型,该值大小写不敏感,必选项,可以是bearer类型或mac类型。
expires_in:表示过期时间,单位为秒。如果省略该参数,必须其他方式设置过期时间。
refresh_token:表示更新令牌,用来获取下一次的访问令牌,可选项。
scope:表示权限范围,如果与客户端申请的范围一致,此项可省略。
下面是一个例子。
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
“access_token”:“2YotnFZFEjr1zCsicMWpAA”,
“token_type”:“example”,
“expires_in”:3600,
“refresh_token”:“tGzv3JOkF0XG5Qx2TlKWIA”,
“example_parameter”:“example_value”
}
从上面代码可以看到,相关参数使用JSON格式发送(Content-Type: application/json)。此外,HTTP头信息中明确指定不得缓存。
springsecurity oauth2文档向导 https://projects.spring.io/spring-security-oauth/docs/oauth2.html
整个工程包括三个独立的应用,一个认证服务和两个客户端应用,结构非常简单。当一个用户访问客户端应用中被防护的API时,系统会被自动重定向到认证服务,之后我们使用OAuth2.0的Authorization code授权方式来实现认证授权。
英语文章介绍官网:https://projects.spring.io/spring-security-oauth/docs/oauth2.html
Spring OAuth2.0提供者实际上分为:
虽然这两个提供者有时候可能存在同一个应用程序中,但在Spring Security OAuth中你可以把
他它们各自放在不同的应用上,而且你可以有多个资源服务,它们共享同一个中央授权服
务。
所有获取令牌的请求都将会在Spring MVC controller endpoints中进行处理,并且访问受保护
的资源服务的处理流程将会放在标准的Spring Security请求过滤器中(filters)。
下面是配置一个授权服务必须要实现的endpoints(就是控制层,有@RequstMapping注解):
下面是配置一个资源服务必须要实现的过滤器:
配置提供者(授权、资源)都可以通过简单的Java注解@Configuration来进行适配,你也可以使用基于XML的声明式语法来进行配置,如果你打算这样做的话,那么请使用http://www.springframework.org/schema/security/spring-security-oauth2.xsd来作为XML的schema(即XML概要定义)以及使用http://www.springframework.org/schema/security/oauth2来作为命名空间。
配置一个授权服务,你需要考虑几种授权类型(Grant Type),不同的授权类型为客户端(Client)提供了不同的获取令牌(Token)方式,为了实现并确定这几种授权,需要配置使用 ClientDetailsService 和 TokenService 来开启或者禁用这几种授权机制。到这里就请注意了,不管你使用什么样的授权类型(Grant Type),每一个客户端(Client)都能够通过明确的配置以及权限来实现不同的授权访问机制。这也就是说,假如你提供了一个支持"client_credentials"的授权方式,并不意味着客户端就需要使用这种方式来获得授权。下面是几种授权类型的列表,具体授权机制的含义可以参见RFC6749(中文版本):
可以用 @EnableAuthorizationServer 注解来配置OAuth2.0 授权服务机制,通过使用@Bean注解的几个方法一起来配置这个授权服务。下面咱们介绍几个配置类,这几个配置是由Spring创建的独立的配置对象,它们会被Spring传入AuthorizationServerConfigurer中:
配置授权服务一个比较重要的方面就是提供一个授权码给一个OAuth客户端(通过 authorization_code 授权类型),一个授权码的获取是OAuth客户端跳转到一个授权页面,然后通过验证授权之后服务器重定向到OAuth客户端,并且在重定向连接中附带返回一个授权码。
如果你是通过XML来进行配置的话,那么可以使用 标签来进行配置。
(译者注:想想现在国内各大平台的社会化登陆服务,例如腾讯,用户要使用QQ登录到某个网站,这个网站是跳转到了腾讯的登陆授权页面,然后用户登录并且确定授权之后跳转回目标网站,这种授权方式规范在我上面提供的链接RFC6749的第4.1节有详细阐述。)
ClientDetailsServiceConfigurer (AuthorizationServerConfigurer 的一个回调配置项,见上的概述) 能够使用内存或者JDBC来实现客户端详情服务(ClientDetailsService),有几个重要的属性如下列表:
客户端详情(Client Details)能够在应用程序运行的时候进行更新,可以通过访问底层的存储服务(例如将客户端详情存储在一个关系数据库的表中,就可以使用 JdbcClientDetailsService)或者通过 ClientDetailsManager 接口(同时你也可以实现 ClientDetailsService 接口)来进行管理。
(译者注:不过我并没有找到 ClientDetailsManager 这个接口文件,只找到了 ClientDetailsService)
AuthorizationServerTokenServices 接口定义了一些操作使得你可以对令牌进行一些必要的管理,在使用这些操作的时候请注意以下几点:
当你自己创建 AuthorizationServerTokenServices 这个接口的实现时,你可能需要考虑一下使用 DefaultTokenServices 这个类,里面包含了一些有用实现,你可以使用它来修改令牌的格式和令牌的存储。默认的,当它尝试创建一个令牌的时候,是使用随机值来进行填充的,除了持久化令牌是委托一个 TokenStore 接口来实现以外,这个类几乎帮你做了所有的事情。并且 TokenStore 这个接口有一个默认的实现,它就是 InMemoryTokenStore ,如其命名,所有的令牌是被保存在了内存中。除了使用这个类以外,你还可以使用一些其他的预定义实现,下面有几个版本,它们都实现了TokenStore接口:
使用JWT令牌你需要在授权服务中使用 JwtTokenStore,资源服务器也需要一个解码的Token令牌的类 JwtAccessTokenConverter,JwtTokenStore依赖这个类来进行编码以及解码,因此你的授权服务以及资源服务都需要使用这个转换类。Token令牌默认是有签名的,并且资源服务需要验证这个签名,因此呢,你需要使用一个对称的Key值,用来参与签名计算,这个Key值存在于授权服务以及资源服务之中。或者你可以使用非对称加密算法来对Token进行签名,Public Key公布在/oauth/token_key这个URL连接中,默认的访问安全规则是"denyAll()",即在默认的情况下它是关闭的,你可以注入一个标准的 SpEL 表达式到 AuthorizationServerSecurityConfigurer 这个配置中来将它开启(例如使用"permitAll()"来开启可能比较合适,因为它是一个公共密钥)。
如果你要使用 JwtTokenStore,请务必把"spring-security-jwt"这个依赖加入到你的classpath中。
授权是使用 AuthorizationEndpoint 这个端点来进行控制的,你能够使用 AuthorizationServerEndpointsConfigurer 这个对象的实例来进行配置(AuthorizationServerConfigurer 的一个回调配置项,见上的概述) ,如果你不进行设置的话,默认是除了资源所有者密码(password)授权类型以外,支持其余所有标准授权类型的(RFC6749),我们来看一下这个配置对象有哪些属性可以设置吧,如下列表:
在XML配置中呢,你可以使用 “authorization-server” 这个标签元素来进行设置。
AuthorizationServerEndpointsConfigurer 这个配置对象(AuthorizationServerConfigurer 的一个回调配置项,见上的概述) 有一个叫做 pathMapping() 的方法用来配置端点URL链接,它有两个参数:
需要注意的是授权端点这个URL应该被Spring Security保护起来只供授权用户访问,我们来看看在标准的Spring Security中 WebSecurityConfigurer 是怎么用的。
复制代码
@Override
protected void configure(HttpSecurity http) throws Exception {
http .authorizeRequests().antMatchers("/login").permitAll().and()
// default protection for all resources (including /oauth/authorize)
.authorizeRequests() .anyRequest().hasRole("USER")
// ... more configuration, e.g. for form login
}
复制代码
注意:如果你的应用程序中既包含授权服务又包含资源服务的话,那么这里实际上是另一个的低优先级的过滤器来控制资源接口的,这些接口是被保护在了一个访问令牌(access token)中,所以请挑选一个URL链接来确保你的资源接口中有一个不需要被保护的链接用来取得授权,就如上面示例中的 /login 链接,你需要在 WebSecurityConfigurer 配置对象中进行设置。
令牌端点默认也是受保护的,不过这里使用的是基于 HTTP Basic Authentication 标准的验证方式来验证客户端的,这在XML配置中是无法进行设置的(所以它应该被明确的保护)。
在XML配置中可以使用 元素标签来改变默认的端点URLs,注意在配置 /check_token 这个链接端点的时候,使用 check-token-enabled 属性标记启用。
使用简单的HTTP请求来进行测试是可以的,但是如果你要部署到产品环境上的时候,你应该永远都使用SSL来保护授权服务器在与客户端进行通讯的时候进行加密。你可以把授权服务应用程序放到一个安全的运行容器中,或者你可以使用一个代理,如果你设置正确了的话它们应该工作的很好(这样的话你就不需要设置任何东西了)。
但是也许你可能希望使用 Spring Security 的 requiresChannel() 约束来保证安全,对于授权端点来说(还记得上面的列表吗,就是那个 /authorize 端点),它应该成为应用程序安全连接的一部分,而对于 /token 令牌端点来说的话,它应该有一个标记被配置在 AuthorizationServerEndpointsConfigurer 配置对象中,你可以使用 sslOnly() 方法来进行设置。当然了,这两个设置是可选的,不过在以上两种情况中,会导致Spring Security 会把不安全的请求通道重定向到一个安全通道中。(译者注:即将HTTP请求重定向到HTTPS请求上)。
端点实际上就是一个特殊的Controller,它用于返回一些对象数据。
授权服务的错误信息是使用标准的Spring MVC来进行处理的,也就是 @ExceptionHandler 注解的端点方法,你也可以提供一个 WebResponseExceptionTranslator 对象。最好的方式是改变响应的内容而不是直接进行渲染。
假如说在呈现令牌端点的时候发生了异常,那么异常委托了 HttpMessageConverters 对象(它能够被添加到MVC配置中)来进行输出。假如说在呈现授权端点的时候未通过验证,则会被重定向到 /oauth/error 即错误信息端点中。whitelabel error (即Spring框架提供的一个默认错误页面)错误端点提供了HTML的响应,但是你大概可能需要实现一个自定义错误页面(例如只是简单的增加一个 @Controller 映射到请求路径上 @RequestMapping("/oauth/error"))。
有时候限制令牌的权限范围是很有用的,这不仅仅是针对于客户端,你还可以根据用户的权限来进行限制。如果你使用 DefaultOAuth2RequestFactory 来配置 AuthorizationEndpoint 的话你可以设置一个flag即 checkUserScopes=true来限制权限范围,不过这只能匹配到用户的角色。你也可以注入一个 OAuth2RequestFactory 到 TokenEnpoint 中,不过这只能工作在 password 授权模式下。如果你安装一个 TokenEndpointAuthenticationFilter 的话,你只需要增加一个过滤器到 HTTP BasicAuthenticationFilter 后面即可。当然了,你也可以实现你自己的权限规则到 scopes 范围的映射和安装一个你自己版本的 OAuth2RequestFactory。AuthorizationServerEndpointConfigurer 配置对象允许你注入一个你自定义的 OAuth2RequestFactory,因此你可以使用这个特性来设置这个工厂对象,前提是你使用 @EnableAuthorizationServer 注解来进行配置(见上面介绍的授权服务配置)。
授权服务器上必须有登录的用户名和密码信息(只有已经登录的用户才能使用授权),并且配置允许授权的客户端信息
具体过程参考 :
添加maven依赖
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.0.8.RELEASE</version> </parent> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.security.oauth.boot</groupId> <artifactId>spring-security-oauth2-autoconfigure</artifactId> <version>2.1.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> <version>1.2.3.RELEASE</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>nekohtml</groupId> <artifactId>nekohtml</artifactId> <version>1.9.6.2</version> </dependency> </dependencies>
添加springsecurity的配置类
package lzeqian; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.provisioning.InMemoryUserDetailsManager; /** * @author 廖敏 * 创建日期 2019-03-12 16:15 **/ @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { public UserDetailsService userDetailsService11() { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User.withUsername("test").password(new BCryptPasswordEncoder().encode("123456")).roles("USER").build()); return manager; } @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/test").authenticated() .regexMatchers("/images/.+\\.jpg").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/rlogin") .loginProcessingUrl("/myauth") .failureForwardUrl("/rlogin") .usernameParameter("user") .passwordParameter("pass") .permitAll() //注意 这里用false因为登录成功后,调回之前输入的授权页面 .defaultSuccessUrl("/toSuc",false) .and() .httpBasic().and().csrf().disable(); } protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService11()).passwordEncoder(new BCryptPasswordEncoder()); } }
添加控制层的登录和成功页面
@Controller
public class TestController {
@RequestMapping("/toSuc")
public String suc() {
return "suc";
}
@RequestMapping("/rlogin")
public String rlogin(String name) {
return "lg";
}
}
添加登录模板/src/main/resource/templates/lg.html
<!DOCTYPE html> <html> <head> <title></title> <meta charset="utf-8"> <style type="text/css"> fieldset{width: 350px;background:#4876FF;height: 200px;margin: 150px 400px 0px} button{maogin: 0; padding: 0} </style> </head> <body bgcolor="#7EC0EE"> <form action="/myauth" method="post"> <label>用户名:</label> <input type="text" name="user" value="test">(请输入用户名)</br></br> <label>密 码:</label> <input type="password" name="pass" value="123456">(输入密码)<br><br> <input type="submit" value="提交"> </form> </body> </html>
添加成功页面模板suc.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
this is success page
</body>
</html>
application.properties配置
server.port=8889
spring.thymeleaf.cache=false
spring.thymeleaf.mode=LEGACYHTML5
测试登录是否跳转到登录页,输入用户名test和密码123456是否能成功跳转到成功页面
添加认证服务器配置类
@Configuration @EnableAuthorizationServer public class OAuth2AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter { /** 配置客户端详情服务 **/ @Override public void configure(ClientDetailsServiceConfigurer clients) throws Exception { //添加客户端信息 clients.inMemory() // 使用in-memory存储客户端信息 .withClient("client") // 获取授权码必须指定的客户端标识 .secret("{noop}secret") // client_secret 需要加上{noop}指定使用NoOpPasswordEncoder给DelegatingPasswordEncoder去校验密码 .authorizedGrantTypes("authorization_code") // 该client允许的授权类型 .redirectUris("http://www.baidu.com") .scopes("all"); // 允许的授权范围 } /** 用来配置授权(authorization)以及令牌(token)的访问端点和令牌服务(token services)。 **/ @Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { super.configure(endpoints); endpoints.tokenStore(new JwtTokenStore(new JwtAccessTokenConverter())); } /** 用来配置令牌端点(Token Endpoint)的安全约束. **/ @Override public void configure(AuthorizationServerSecurityConfigurer security) throws Exception { super.configure(security); //security.passwordEncoder(NoOpPasswordEncoder.getInstance()); } }
接下来模拟客户端获取授权码
localhost:8889/oauth/authorize?client_id=client&response_type=code&redirect_uri=http://www.baidu.com
因为第一次访问,直接跳转到登陆页面
输入用户名和密码后,跳转到用户授权页面
点击 authrize 授权 跳转到www.baidu.com带上了code授权码
接下来模拟通过授权码获取token,如果linux可以通过
curl -X POST -H "Content-Type: application/x-www-form-urlencoded" -d
'grant_type=authorization_code&code=uHJ1Lt&redirect_uri=http://www.baidu.com'
"http://client:secret@localhost:8889/oauth/token"
也可以通过idea的rest测试工具
填写参数点击左上角绿色测试箭头
在response也签中返回了token
{"access_token":"cd51e083-95b2-4c71-9774-5a07de116345","token_type":"bearer","expires_in":43199,"scope":"all"}
如果希望在token上带上额外信息
修改oauth2配置类
@Override public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception { super.configure(endpoints); endpoints.tokenStore(new JwtTokenStore(new JwtAccessTokenConverter())) //添加额外信息到token中 .tokenEnhancer(new TokenEnhancer() { @Override public OAuth2AccessToken enhance(OAuth2AccessToken oAuth2AccessToken, OAuth2Authentication oAuth2Authentication) { DefaultOAuth2AccessToken doat= (DefaultOAuth2AccessToken) oAuth2AccessToken; Map<String, Object> additionalInfo = new HashMap<>(); additionalInfo.put("myname", "jiaozi"); doat.setAdditionalInformation(additionalInfo); return doat; } }) ; }
再次测试返回
{"access_token":"a675a848-227d-4951-937c-5d2b36282c26","token_type":"bearer","expires_in":43199,"scope":"all","myname":"jiaozi"}
jdbc对应表:https://github.com/spring-projects/spring-security-oauth/blob/master/spring-security-oauth2/src/test/resources/schema.sql
配置data-jpa,配置数据源(不给出)
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.withClientDetails(new JdbcClientDetailsService(配置DataSource类));
}
默认授权页
通过文章 https://projects.spring.io/spring-security-oauth/docs/oauth2.html 了解
The URL paths provided by the framework are /oauth/authorize (the authorization endpoint), /oauth/token (the token endpoint), /oauth/confirm_access (user posts approval for grants here), /oauth/error (used to render errors in the authorization server), /oauth/check_token (used by Resource Servers to decode access tokens), and /oauth/token_key (exposes public key for token verification if using JWT tokens).
实际授权页的路径是:/oauth/confirm_access
可以替换成自己的授权页
修改oauth2入口
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
endpoints.pathMapping("/oauth/confirm_access ","/extenal/oauth/confirm_access")///extenal表示外部的
}
定义一个控制层类 路径是:/oauth/confirm_access
@Controller
public class OAuth2ApprovalController {
@RequestMapping("/oauth/confirm_access")
public String getAccessConfirmation(Map<String, Object> model, HttpServletRequest request)
throws Exception {
return "approval";
}
}
新建approval的html页面
<html>
<body><h1>OAuth 确认</h1>
<p>您是否希望 "client" 访问受保护的资源?</p>
<form id="confirmationForm" name="confirmationForm" action="/oauth/authorize" method="post"><input
name="user_oauth_approval" value="true" type="hidden"/><label>
<input name="authorize" value="授权"
type="submit"/></label></form>
<form id="denialForm" name="denialForm" action="/oauth/authorize" method="post">
<input name="user_oauth_approval"
value="false" type="hidden"/><label>
<input
name="deny" value="禁止" type="submit"/></label></form>
</body>
</html>
最后授权页面
如果不配置入口也可以直接定义接口但是要使用SessionAttributes注解
@Controller
@SessionAttributes("authorizationRequest")
public class OAuth2ApprovalController {
@RequestMapping("/oauth/confirm_access")
public String getAccessConfirmation(Map<String, Object> model, HttpServletRequest request)
throws Exception {
return "approval";
}
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。