Secure REST API with oauth2 (翻译)
http://blog.csdn.net/haiyan_qi/article/details/52384734
**************************************************
1.概述
运用AngularJS和springboot技术实现的demo:
https://github.com/qihaiyan/ng-boot-oauth
在这个教程中,我们将用oauth2对REST API进行安全控制,并在一个简单的AngularJS客户端程序中使用。
我们将要构建的应用包含四个独立的模块:
- Authorization Server
- Resource Server
- UI implicit – 使用 Implicit Flow 的前端应用
- UI password – 使用 Password Flow 的前端应用
2.认证服务
我们开始用spring Boot构建一个认证服务。
2.1 Maven配置
Maven依赖配置如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>${oauth.version}</version>
</dependency>
注意我们采用了spring-jdbc和MySQL,因为我们会使用jdbc来实现token store。
2.2. @EnableAuthorizationServer
配置用于管理access tokens的认证服务:
@Configuration
@EnableAuthorizationServer
public class AuthServerOAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager; @Override
public void configure(
AuthorizationServerSecurityConfigurer oauthServer)
throws Exception {
oauthServer
.tokenKeyAccess("permitAll()")
.checkTokenAccess("isAuthenticated()");
} @Override
public void configure(ClientDetailsServiceConfigurer clients)
throws Exception {
clients.jdbc(dataSource())
.withClient("sampleClientId")
.authorizedGrantTypes("implicit")
.scopes("read")
.autoApprove(true)
.and()
.withClient("clientIdPassword")
.secret("secret")
.authorizedGrantTypes(
"password","authorization_code", "refresh_token")
.scopes("read");
} @Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints)
throws Exception {
endpoints
.tokenStore(tokenStore())
.authenticationManager(authenticationManager);
} @Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource());
}
}
解释:
- 用JdbcTokenStore来存储tokens
- 注册一个采用“implicit”授权方式的客户端
- 注册另一个采用 “password“, “authorization_code” 和 “refresh_token”授权方式的客户端
- 为了使用“password”授权方式,我们需要通过spring的@Autowired注解来注入和使用AuthenticationManagerbean
2.3. 数据源配置
配置JdbcTokenStore用到的数据源
@Value("classpath:schema.sql")
private Resource schemaScript; @Bean
public DataSourceInitializer dataSourceInitializer(DataSource dataSource) {
DataSourceInitializer initializer = new DataSourceInitializer();
initializer.setDataSource(dataSource);
initializer.setDatabasePopulator(databasePopulator());
return initializer;
} private DatabasePopulator databasePopulator() {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(schemaScript);
return populator;
} @Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.user"));
dataSource.setPassword(env.getProperty("jdbc.pass"));
return dataSource;
}
注意:使用JdbcTokenStore 时,我们需要初始化数据库并创建相关的表来存储token数据,通过使用DataSourceInitializer 和下面的语句来实现:
drop table if exists oauth_client_details;
create table oauth_client_details (
client_id VARCHAR(255) PRIMARY KEY,
resource_ids VARCHAR(255),
client_secret VARCHAR(255),
scope VARCHAR(255),
authorized_grant_types VARCHAR(255),
web_server_redirect_uri VARCHAR(255),
authorities VARCHAR(255),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additional_information VARCHAR(4096),
autoapprove VARCHAR(255)
); drop table if exists oauth_client_token;
create table oauth_client_token (
token_id VARCHAR(255),
token LONG VARBINARY,
authentication_id VARCHAR(255) PRIMARY KEY,
user_name VARCHAR(255),
client_id VARCHAR(255)
); drop table if exists oauth_access_token;
create table oauth_access_token (
token_id VARCHAR(255),
token LONG VARBINARY,
authentication_id VARCHAR(255) PRIMARY KEY,
user_name VARCHAR(255),
client_id VARCHAR(255),
authentication LONG VARBINARY,
refresh_token VARCHAR(255)
); drop table if exists oauth_refresh_token;
create table oauth_refresh_token (
token_id VARCHAR(255),
token LONG VARBINARY,
authentication LONG VARBINARY
); drop table if exists oauth_code;
create table oauth_code (
code VARCHAR(255), authentication LONG VARBINARY
); drop table if exists oauth_approvals;
create table oauth_approvals (
userId VARCHAR(255),
clientId VARCHAR(255),
scope VARCHAR(255),
status VARCHAR(10),
expiresAt TIMESTAMP,
lastModifiedAt TIMESTAMP
); drop table if exists ClientDetails;
create table ClientDetails (
appId VARCHAR(255) PRIMARY KEY,
resourceIds VARCHAR(255),
appSecret VARCHAR(255),
scope VARCHAR(255),
grantTypes VARCHAR(255),
redirectUrl VARCHAR(255),
authorities VARCHAR(255),
access_token_validity INTEGER,
refresh_token_validity INTEGER,
additionalInformation VARCHAR(4096),
autoApproveScopes VARCHAR(255)
);
2.4. 安全权限配置
最后,为认证服务增加安全权限控制功能。
当客户端程序需要获取Access Token时,会执行下面一个简单的from-login驱动的认证过程:
@Configuration
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter { @Override
protected void configure(AuthenticationManagerBuilder auth)
throws Exception {
auth.inMemoryAuthentication()
.withUser("john").password("123").roles("USER");
} @Override
@Bean
public AuthenticationManager authenticationManagerBean()
throws Exception {
return super.authenticationManagerBean();
} @Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/login").permitAll()
.anyRequest().authenticated()
.and()
.formLogin().permitAll();
}
}
需要注意对于oauth2的Password flow模式,from-login配置不是必须的,只对Implicit flow是必须的。
3. Resource 服务
Resource 服务用于提供REST API。
3.1. Maven 配置
Resource 服务的Maven配置与前面的认证服务的Maven配置相同。
3.2. Token Store 配置
TokenStore 采用与前面的认证服务相同的数据源
@Autowired
private Environment env; @Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(env.getProperty("jdbc.driverClassName"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setUsername(env.getProperty("jdbc.user"));
dataSource.setPassword(env.getProperty("jdbc.pass"));
return dataSource;
} @Bean
public TokenStore tokenStore() {
return new JdbcTokenStore(dataSource());
}
为了简化起见,虽然认证服务和Resource 服务是两个独立的应用程序,但是用了同一个数据库,原因是Resource 服务需要验证认证服务中生成的access token。
3.3. Remote Token Service
除了在Resource服务中使用TokenStore 之外,还可以使用RemoteTokeServices:
@Primary
@Bean
public RemoteTokenServices tokenService() {
RemoteTokenServices tokenService = new RemoteTokenServices();
tokenService.setCheckTokenEndpointUrl(
"http://localhost:8080/spring-security-oauth-server/oauth/check_token");
tokenService.setClientId("fooClientIdPassword");
tokenService.setClientSecret("secret");
return tokenService;
}
注意:
- RemoteTokenService会使用认证服务中的CheckToken节点去验证AccessToken并获取 Authentication对象.
*CheckToken节点的访问地址为:认证服务器的URL +”/oauth/check_token“ - 认证服务可以使用任意的TokenStore类型,包括 [JdbcTokenStore, JwtTokenStore, …] ,不会影响到RemoteTokenService 或 Resource 服务
3.4. 一个简单的 Controller
下面用一个简单的Controller来提供Foo 接口
@Controller
public class FooController { @PreAuthorize("#oauth2.hasScope('read')")
@RequestMapping(method = RequestMethod.GET, value = "/foos/{id}")
@ResponseBody
public Foo findById(@PathVariable long id) {
return
new Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4));
}
}
使用这个接口的客户端需要具有“read”权限。
同时需要启用全局安全权限控制,并且需要配置MethodSecurityExpressionHandler:
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig
extends GlobalMethodSecurityConfiguration { @Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
return new OAuth2MethodSecurityExpressionHandler();
}
}
Foo接口的实现如下:
public class Foo {
private long id;
private String name;
}
3.5. Web 配置
为API提供一个基础的web配置:
@Configuration
@EnableWebMvc
@ComponentScan({ "org.baeldung.web.controller" })
public class ResourceWebConfig extends WebMvcConfigurerAdapter {}
4. 客户端应用 – 用户密码模式
下面来看一下用AngularJS实现的简单的客户端应用程序。
我们将采用OAuth2的Password flow认证方式,用户的用户名和密码信息将会暴露给客户端应用(这是不安全的)。
首先创建两个简单的页面 - “index” 和 “login”,用户在页面上录入凭证信息,前端的JS程序用这些凭证信息去认证服务上获取Access Token。
4.1. 登录页面
<body ng-app="myApp" ng-controller="mainCtrl">
<h1>Login</h1>
<label>Username</label><input ng-model="data.username"/>
<label>Password</label><input type="password" ng-model="data.password"/>
<a href="#" ng-click="login()">Login</a>
</body>
4.2. 获取 Access Token
下面来看一下怎么获取 access token:
var app = angular.module('myApp', ["ngResource","ngRoute","ngCookies"]);
app.controller('mainCtrl',
function($scope, $resource, $http, $httpParamSerializer, $cookies) { $scope.data = {
grant_type:"password",
username: "",
password: "",
client_id: "clientIdPassword"
};
$scope.encoded = btoa("clientIdPassword:secret"); $scope.login = function() {
var req = {
method: 'POST',
url: "http://localhost:8080/spring-security-oauth-server/oauth/token",
headers: {
"Authorization": "Basic " + $scope.encoded,
"Content-type": "application/x-www-form-urlencoded; charset=utf-8"
},
data: $httpParamSerializer($scope.data)
}
$http(req).then(function(data){
$http.defaults.headers.common.Authorization =
'Bearer ' + data.data.access_token;
$cookies.put("access_token", data.data.access_token);
window.location.href="index";
});
}
});
解释:
- 通过提交一个 POST 请求到 “/oauth/token” 来获取Access Token
- 使用客户端凭证和 Basic Auth
- 通过 url encode 对用户凭证、客户端 id 和 grant type进行编码
- 得到Access Token后将其存放到cookie中
4.3. Index 页面
<body ng-app="myApp" ng-controller="mainCtrl">
<h1>Foo Details</h1>
<label>ID</label><span>{{foo.id}}</span>
<label>Name</label><span>{{foo.name}}</span>
<a href="#" ng-click="getFoo()">New Foo</a>
</body>
4.4. 对客户端请求进行授权
因为Resource服务需要使用access token对客户端请求进行授权验证,我们用access token在http头中增加一个简单的authorization header:
var isLoginPage = window.location.href.indexOf("login") != -1;
if(isLoginPage){
if($cookies.get("access_token")){
window.location.href = "index";
}
} else{
if($cookies.get("access_token")){
$http.defaults.headers.common.Authorization =
'Bearer ' + $cookies.get("access_token");
} else{
window.location.href = "login";
}
}
如果没找到cookie,将重定向到login页面。
5. 客户端应用 – 简化模式
下面来看一下采用OAuth2简化模式的客户端程序。
这个程序是一个单独的模块,采用oauth2的简化模式,从认证服务中获取access token,然后用这个access token去访问Resource服务。
5.1. Maven 配置
这是pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
这儿不需要spring的oauth模块,我们将使用AngularJS的OAuth-ng directive,以implicit grant flow方式去访问oauth2 认证服务。
5.2. Web 配置
@Configuration
@EnableWebMvc
public class UiWebConfig extends WebMvcConfigurerAdapter {
@Bean
public static PropertySourcesPlaceholderConfigurer
propertySourcesPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
} @Override
public void configureDefaultServletHandling(
DefaultServletHandlerConfigurer configurer) {
configurer.enable();
} @Override
public void addViewControllers(ViewControllerRegistry registry) {
super.addViewControllers(registry);
registry.addViewController("/index");
registry.addViewController("/oauthTemplate");
} @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/resources/");
}
}
5.3. Home 页面
OAuth-ng directive需要以下参数:
- site: 认证服务的URL
- client-id: 客户端应用的 client id
- redirect-uri: 从认证服务获取到access token后,重定向到此URI
- scope: 从认证服务获取到的权限
- template: AngularJS的页面模板
<body ng-app="myApp" ng-controller="mainCtrl">
<oauth
site="http://localhost:8080/spring-security-oauth-server"
client-id="clientId"
redirect-uri="http://localhost:8080/spring-security-oauth-ui-implicit/index"
scope="read"
template="oauthTemplate">
</oauth> <h1>Foo Details</h1>
<label >ID</label><span>{{foo.id}}</span>
<label>Name</label><span>{{foo.name}}</span>
</div>
<a href="#" ng-click="getFoo()">New Foo</a> <script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js">
</script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-resource.min.js">
</script>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular-route.min.js">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/ngStorage/0.3.9/ngStorage.min.js">
</script>
<script th:src="@{/resources/oauth-ng.js}"></script>
</body>
现在说明如何用OAuth-ng directive来获取AccessToken,这是一个简单oauthTemplate.html:
<div>
<a href="#" ng-show="show=='logged-out'" ng-click="login()">Login</a>
<a href="#" ng-show="show=='denied'" ng-click="login()">Access denied. Try again.</a>
</div>
5.4. AngularJS 应用
var app = angular.module('myApp', ["ngResource","ngRoute","oauth"]);
app.config(function($locationProvider) {
$locationProvider.html5Mode({
enabled: true,
requireBase: false
}).hashPrefix('!');
}); app.controller('mainCtrl', function($scope,$resource,$http) {
$scope.$on('oauth:login', function(event, token) {
$http.defaults.headers.common.Authorization= 'Bearer ' + token.access_token;
}); $scope.foo = {id:0 , name:"sample foo"};
$scope.foos = $resource(
"http://localhost:8080/spring-security-oauth-resource/foos/:fooId",
{fooId:'@id'});
$scope.getFoo = function(){
$scope.foo = $scope.foos.get({fooId:$scope.foo.id});
}
});
获取到Access Token后,通过http头的Authorization header来访问Resrouce服务中提供的接口服务。
6. 总结
至此我们阐述了如果使用OAuth2来为应用程序提供安全权限控制功能。
本文的所有实例代码在the github project 中 - 这是一个eclipse项目,可以直接导入并运行。
https://github.com/Baeldung/spring-security-oauth
Secure REST API with oauth2 (翻译)的更多相关文章
- 基于百度通用翻译API的一个翻译小工具
前几天写了一个简单的翻译小工具,是基于有道翻译的,不过那个翻译接口有访问限制,超过一定次数后会提示访问过于频繁,偶然发现百度翻译API如果月翻译字符少于200万是不收取费用的,所以就注册了一个百度开发 ...
- 利用百度翻译API,获取翻译结果
利用百度翻译API,获取翻译结果 translate.py #!/usr/bin/python #-*- coding:utf-8 -*- import sys reload(sys) sys.set ...
- 利用有道翻译Api实现英文翻译功能
有道翻译提供了翻译和查词的数据接口.通过数据接口,您可以获得一段文本的翻译结果或者查词结果. 通过调用有道翻译API数据接口,您可以在您的网站或应用中更灵活地定制翻译和查词功能. 第一步: ...
- C++调用有道翻译API实现在线翻译之发声篇
大概半月前写了一篇博文:C++中使用Curl和JsonCpp调用有道翻译API实现在线翻译, 得到大家的热情捧场,有人看了文章说要是能发声不是更好,我觉得说的也是哈,能听到专家的标准发音,那该是多美的 ...
- C++中使用Curl和JsonCpp调用有道翻译API实现在线翻译
使用C++开发一个在线翻译工具,这个想法在我大脑中过了好几遍了,所以就搜了下资料,得知网络上有很多翻译API,这里我选择我平时使用较多的有道翻译API进行在线翻译工具开发的练习.翻译API返回的结果常 ...
- 使用python在SAE上搭建一个微信应用,使用有道翻译的api进行在线翻译
1. 准备,先在使用python一步一步搭建微信公众平台(一)中基本实现自动回复的功能后,接着在有道词典上申请一个key,http://fanyi.youdao.com/openapi?path=da ...
- ecshop使用Google API及OAuth2.0登录授权(PHP)
一.申请clientID https://console.developers.google.com/project 二.开启Google+ API权限 https://console.develop ...
- 日常API之百度翻译
百度翻译是什么,可以吃吗?相信很多人都熟悉,它是我们生活中必不可少的一只东东. 但是,百度翻译开发平台只有每月只能翻译200万个字符,多出的要按照49.00/百万字符来算.对于我酱紫的乞丐程序员来说, ...
- 原生js简单调用百度翻译API实现的翻译工具
先来个在线demo: js翻译工具 或者百度搜索js简单调用百度翻译API工具(不过有个小小的界面显示bug,我想细心的人应该会发现) 或者直接前往该网址:js翻译工具 或者前往我的github:gi ...
随机推荐
- 使用HttpClient发送请求接收响应
1.一般需要如下几步:(1) 创建HttpClient对象.(2)创建请求方法的实例,并指定请求URL.如果需要发送GET请求,创建HttpGet对象:如果需要发送POST请求,创建HttpPost对 ...
- 数学图形(1.39)TN constant curve
这是个类似巴黎铁塔的曲线. #http://www.mathcurve.com/courbes2d/tn/tn.shtml vertices = t = to (PI*0.999) a = s = s ...
- Delphi DBGrid图显用法
procedure TForm10.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;DataCol: Integer; Column: ...
- 使用PowerDesigner建立数据库模型【转】
1. 打开PowerDesigner,点击File->New 2. 选择Conceptual Data Model,并修改Model name. 3. 在Palette工具栏中点击Entity ...
- phpstorm failed to create jvm:error code -6 解决办法 解决方法
phpStorm 软件打开运行提示 failed to create JVM的解决办法. 修改文件 D:\Program Files (x86)\JetBrains\PhpStorm 7.1.3\bi ...
- 利用VS2010开发一个跳转页面aspx
在开发项目的过程中,由于要集成Cognos的报表通过URL,但是Cognos报表的本身URL长度过程,那么此时就需要开发一个跳转的页面,下面我们就采用VS2010开发一个跳转的页面Default.as ...
- C#控件一览表
C#控件一览表 .窗体 .常用属性 ()Name属性:用来获取或设置窗体的名称,在应用程序中可通过Name属性来引用窗体. () WindowState属性: 用来获取或设置窗体的窗口状态. 取值有三 ...
- python2.7里的StringIO.StringIO与BytesIO有什么区别
import StringIO与from io import BytesIO的区别 open()函数返回的文件对象取决于模式.当使用文本模式打开文件时,它返回一个TextIOBase的子类.当使用二进 ...
- TP框架ajax U方法不解析怎么办?
TP框架中ajax U方法不解析 ajax U方法不解析 ajax url不解析 问题: 造成问题原因: Js 存在单独的 js文件中和html分离了.造成不解析! 解决方法: 方法一:将js放到ht ...
- Patterns-Observer
http://book.javanb.com/java-design-patterns/index.html Java深入到一定程度,就不可避免的碰到设计模式(design pattern)这一概念, ...