使用OAuth保护REST API并使用简单的Angular客户端
1.概述
在本教程中,我们将使用OAuth保护REST API并从简单的Angular客户端使用它。
我们要构建的应用程序将包含四个独立的模块:
- 授权服务器
- 资源服务器
- UI implicit - 使用implicit流的前端应用程序
- UI密码 - 使用密码流的前端应用程序
在我们开始之前 -** 一个重要的注意事项。请记住,Spring Security核心团队正在实施新的OAuth2堆栈 - 某些方面已经完成,有些方面仍在进行中**。
这是一个快速视频,将为您提供有关该工作的一些背景信息:
https://youtu.be/YI4YCJoOF0k
2.授权服务器
首先,让我们开始将Authorization Server设置为一个简单的Spring Boot应用程序。
2.1。 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>
</dependency>
请注意,我们使用的是spring-jdbc和MySQL,因为我们将使用JDBC支持的令牌存储实现。
2.2。 @EnableAuthorizationServer
现在,让我们开始配置负责管理访问令牌的授权服务器:
@Configuration
@EnableAuthorizationServer
public class AuthServerOAuth2Configextends AuthorizationServerConfigurerAdapter {@Autowired@Qualifier("authenticationManagerBean")private AuthenticationManager authenticationManager;@Overridepublic void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {oauthServer.tokenKeyAccess("permitAll()").checkTokenAccess("isAuthenticated()");}@Overridepublic 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");}@Overridepublic void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {endpoints.tokenStore(tokenStore()).authenticationManager(authenticationManager);}@Beanpublic TokenStore tokenStore() {return new JdbcTokenStore(dataSource());}
}
注意:
- 为了持久化令牌,我们使用了JdbcTokenStore
- 我们为“implicit”授权类型注册了客户端
- 我们注册了另一个客户端并授权了“password”,“authorization_code”和“refresh_token”授权类型
- 为了使用“密码”授权类型,我们需要连接并使用AuthenticationManager bean
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,我们需要初始化数据库模式,因此我们使用了DataSourceInitializer - 以及以下SQL模式:
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)
);
请注意,我们不一定需要显式的DatabasePopulator bean - 我们可以简单地使用schema.sql - Spring Boot默认使用它。
2.4。Security配置
最后,让我们保护授权服务器。
当客户端应用程序需要获取访问令牌时,它将在简单的表单登录驱动的身份验证过程之后执行:
@Configuration
public class ServerSecurityConfig extends WebSecurityConfigurerAdapter {@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.inMemoryAuthentication().withUser("john").password("123").roles("USER");}@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.authorizeRequests().antMatchers("/login").permitAll().anyRequest().authenticated().and().formLogin().permitAll();}
}
这里的一个简单说明是密码流不需要表单登录配置 - 仅适用于隐式流 - 因此您可以根据您正在使用的OAuth2流来跳过它。
3.资源服务器
现在,让我们讨论资源服务器;这本质上是我们最终希望能够使用的REST API。
3.1。 Maven配置
我们的资源服务器配置与先前的授权服务器应用程序配置相同。
3.2。令牌存储配置
接下来,我们将配置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());
}
请注意,对于这个简单的实现,我们共享SQL支持的令牌存储,即使授权和资源服务器是单独的应用程序。
当然,原因是资源服务器需要能够检查授权服务器发出的访问令牌的有效性。
3.3。远程令牌服务
我们可以使用RemoteTokeServices而不是在Resource Server中使用TokenStore:
@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将使用授权服务器上的CheckTokenEndPoint来验证AccessToken并从中获取Authentication对象。
- 可以在AuthorizationServerBaseURL +“/ oauth / check_token”找到
- Authorization Server可以使用任何TokenStore类型[JdbcTokenStore,JwtTokenStore,...] - 这不会影响RemoteTokenService或Resource服务器。
3.4。 Controller样例
接下来,让我们实现一个公开Foo资源的简单控制器:
@Controller
public class FooController {@PreAuthorize("#oauth2.hasScope('read')")@RequestMapping(method = RequestMethod.GET, value = "/foos/{id}")@ResponseBodypublic Foo findById(@PathVariable long id) {returnnew Foo(Long.parseLong(randomNumeric(2)), randomAlphabetic(4));}
}
请注意客户端如何需要“read” scope来访问此资源。
我们还需要启用全局方法安全性并配置MethodSecurityExpressionHandler:
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class OAuth2ResourceServerConfig extends GlobalMethodSecurityConfiguration {@Overrideprotected 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 implements WebMvcConfigurer {}
4.前端 - 设置
我们现在将查看客户端的简单前端Angular实现。
首先,我们将使用Angular CLI生成和管理我们的前端模块。
首先,我们将安装node和npm - 因为Angular CLI是一个npm工具。
然后,我们需要使用frontend-maven-plugin使用maven构建我们的Angular项目:
<build><plugins><plugin><groupId>com.github.eirslett</groupId><artifactId>frontend-maven-plugin</artifactId><version>1.3</version><configuration><nodeVersion>v6.10.2</nodeVersion><npmVersion>3.10.10</npmVersion><workingDirectory>src/main/resources</workingDirectory></configuration><executions><execution><id>install node and npm</id><goals><goal>install-node-and-npm</goal></goals></execution><execution><id>npm install</id><goals><goal>npm</goal></goals></execution><execution><id>npm run build</id><goals><goal>npm</goal></goals><configuration><arguments>run build</arguments></configuration></execution></executions></plugin></plugins>
</build>
最后,使用Angular CLI生成一个新模块:
ng new oauthApp
请注意,我们将有两个前端模块 - 一个用于密码流,另一个用于隐式流。
在以下部分中,我们将讨论每个模块的Angular app逻辑。
5.使用Angular的密码流
我们将在这里使用OAuth2密码流 - 这就是为什么这只是一个概念证明,而不是生产就绪的应用程序。您会注意到客户端凭据已暴露给前端 - 这是我们将在以后的文章中介绍的内容。
我们的用例很简单:一旦用户提供其凭据,前端客户端就会使用它们从授权服务器获取访问令牌。
5.1。应用服务
让我们从位于app.service.ts的AppService开始 - 它包含服务器交互的逻辑:
- obtainAccessToken():获取给定用户凭据的Access令牌
- saveToken():使用ng2-cookies库将访问令牌保存在cookie中
- getResource():使用其ID从服务器获取Foo对象
- checkCredentials():检查用户是否已登录
- logout():删除访问令牌cookie并将用户注销
export class Foo {constructor(public id: number,public name: string) { }
} @Injectable()
export class AppService {constructor(private _router: Router, private _http: Http){}obtainAccessToken(loginData){let params = new URLSearchParams();params.append('username',loginData.username);params.append('password',loginData.password); params.append('grant_type','password');params.append('client_id','fooClientIdPassword');let headers = new Headers({'Content-type': 'application/x-www-form-urlencoded; charset=utf-8','Authorization': 'Basic '+btoa("fooClientIdPassword:secret")});let options = new RequestOptions({ headers: headers });this._http.post('http://localhost:8081/spring-security-oauth-server/oauth/token', params.toString(), options).map(res => res.json()).subscribe(data => this.saveToken(data),err => alert('Invalid Credentials')); }saveToken(token){var expireDate = new Date().getTime() + (1000 * token.expires_in);Cookie.set("access_token", token.access_token, expireDate);this._router.navigate(['/']);}getResource(resourceUrl) : Observable<Foo>{var headers = new Headers({'Content-type': 'application/x-www-form-urlencoded; charset=utf-8','Authorization': 'Bearer '+Cookie.get('access_token')});var options = new RequestOptions({ headers: headers });return this._http.get(resourceUrl, options).map((res:Response) => res.json()).catch((error:any) => Observable.throw(error.json().error || 'Server error'));}checkCredentials(){if (!Cookie.check('access_token')){this._router.navigate(['/login']);}} logout() {Cookie.delete('access_token');this._router.navigate(['/login']);}
}
注意:
- 要获取访问令牌,我们将POST发送到“/ oauth / token”端点
- 我们使用客户端凭据和Basic Auth来命中此端点
- 然后,我们将发送用户凭据以及客户端ID和授予类型参数URL编码
- 获取访问令牌后 - 我们将其存储在cookie中
cookie存储在这里特别重要,因为我们只是将cookie用于存储目的而不是直接驱动身份验证过程。这有助于防止跨站点请求伪造(CSRF)类型的攻击和漏洞。
5.2。登录组件
接下来,让我们看一下负责登录表单的LoginComponent:
@Component({selector: 'login-form',providers: [AppService], template: `<h1>Login</h1><input type="text" [(ngModel)]="loginData.username" /><input type="password" [(ngModel)]="loginData.password"/><button (click)="login()" type="submit">Login</button>`
})
export class LoginComponent {public loginData = {username: "", password: ""};constructor(private _service:AppService) {}login() {this._service.obtainAccessToken(this.loginData);}
5.3。主页组件
接下来,我们的HomeComponent负责显示和操作我们的主页:
@Component({selector: 'home-header',providers: [AppService],template: `<span>Welcome !!</span><a (click)="logout()" href="#">Logout</a><foo-details></foo-details>`
})export class HomeComponent {constructor(private _service:AppService){}ngOnInit(){this._service.checkCredentials();}logout() {this._service.logout();}
}
5.4。 Foo组件
最后,我们的FooComponent显示我们的Foo细节:
@Component({selector: 'foo-details',providers: [AppService], template: `<h1>Foo Details</h1><label>ID</label> <span>{{foo.id}}</span><label>Name</label> <span>{{foo.name}}</span><button (click)="getFoo()" type="submit">New Foo</button>`
})export class FooComponent {public foo = new Foo(1,'sample foo');private foosUrl = 'http://localhost:8082/spring-security-oauth-resource/foos/'; constructor(private _service:AppService) {}getFoo(){this._service.getResource(this.foosUrl+this.foo.id).subscribe(data => this.foo = data,error => this.foo.name = 'Error');}
}
5.5。应用组件
我们的简单AppComponent充当根组件:
@Component({selector: 'app-root',template: `<router-outlet></router-outlet>`
})export class AppComponent {}
以及我们包装所有组件,服务和路由的AppModule:
@NgModule({declarations: [AppComponent,HomeComponent,LoginComponent,FooComponent ],imports: [BrowserModule,FormsModule,HttpModule,RouterModule.forRoot([{ path: '', component: HomeComponent },{ path: 'login', component: LoginComponent }])],providers: [],bootstrap: [AppComponent]
})
export class AppModule { }
6.隐含flow
接下来,我们将重点关注Implicit Flow模块。
6.1。应用服务
同样,我们将从我们的服务开始,但这次我们将使用库angular-oauth2-oidc而不是自己获取访问令牌:
@Injectable()
export class AppService {constructor(private _router: Router, private _http: Http, private oauthService: OAuthService){this.oauthService.loginUrl = 'http://localhost:8081/spring-security-oauth-server/oauth/authorize'; this.oauthService.redirectUri = 'http://localhost:8086/';this.oauthService.clientId = "sampleClientId";this.oauthService.scope = "read write foo bar"; this.oauthService.setStorage(sessionStorage);this.oauthService.tryLogin({}); }obtainAccessToken(){this.oauthService.initImplicitFlow();}getResource(resourceUrl) : Observable<Foo>{var headers = new Headers({'Content-type': 'application/x-www-form-urlencoded; charset=utf-8','Authorization': 'Bearer '+this.oauthService.getAccessToken()});var options = new RequestOptions({ headers: headers });return this._http.get(resourceUrl, options).map((res:Response) => res.json()).catch((error:any) => Observable.throw(error.json().error || 'Server error'));}isLoggedIn(){if (this.oauthService.getAccessToken() === null){return false;}return true;} logout() {this.oauthService.logOut();location.reload();}
}
请注意,在获取访问令牌后,每当我们从资源服务器中使用受保护资源时,我们都会通过Authorization标头使用它。
6.2。主页组件
我们的HomeComponent处理我们简单的主页:
@Component({selector: 'home-header',providers: [AppService],template: `<button *ngIf="!isLoggedIn" (click)="login()" type="submit">Login</button><div *ngIf="isLoggedIn"><span>Welcome !!</span><a (click)="logout()" href="#">Logout</a><br/><foo-details></foo-details></div>`
})export class HomeComponent {public isLoggedIn = false;constructor(private _service:AppService){}ngOnInit(){this.isLoggedIn = this._service.isLoggedIn();}login() {this._service.obtainAccessToken();}logout() {this._service.logout();}
}
6.3。 Foo组件
我们的FooComponent与密码流模块完全相同。
6.4。应用模块
最后,我们的AppModule:
@NgModule({declarations: [AppComponent,HomeComponent,FooComponent ],imports: [BrowserModule,FormsModule,HttpModule,OAuthModule.forRoot(), RouterModule.forRoot([{ path: '', component: HomeComponent }])],providers: [],bootstrap: [AppComponent]
})
export class AppModule { }
7.运行前端
1.要运行我们的任何前端模块,我们需要首先构建应用程序:
mvn clean install
2.然后我们需要导航到我们的Angular app目录:
cd src/main/resources
3.最后,我们将启动我们的应用程序:
npm start
默认情况下,服务器将在端口4200上启动,以更改任何模块的端口更改
"start": "ng serve"
在package.json中使它在端口8086上运行,例如:
"start": "ng serve --port 8086"
8.结论
在本文中,我们学习了如何使用OAuth2授权我们的应用程序。
可以在GitHub项目中找到本教程的完整实现。
转载于:https://www.cnblogs.com/xjknight/p/10959614.html
使用OAuth保护REST API并使用简单的Angular客户端相关推荐
- ASP.NET Web API与Owin OAuth:使用Access Toke调用受保护的API(二)
在前一篇博文中,我们使用OAuth的Client Credential Grant授权方式,在服务端通过CNBlogsAuthorizationServerProvider(Authorization ...
- ASP.NET Web API与Owin OAuth:使用Access Toke调用受保护的API
在前一篇博文中,我们使用OAuth的Client Credential Grant授权方式,在服务端通过CNBlogsAuthorizationServerProvider(Authorization ...
- DreamFacotry 第4章 如何保护REST API
DreamFacotry 第4章 如何保护REST API DreamFactory最受欢迎的功能之一是广泛的身份验证支持.尽管基于API密钥的身份验证足以满足许多DreamFactory驱动的应用程 ...
- 【Ids4实战】分模块保护资源API
(毕竟西湖六月中) 书接上文,上回书咱们说到了IdentityServer4(下文统称Ids4)官方已经从v3更新升级到了v4版本,我的Blog.Idp项目也做了同步更新,主要是针对快速启动UI做的对 ...
- VC API常用函数简单例子大全(1-89)
第一个:FindWindow根据窗口类名或窗口标题名来获得窗口的句柄,该函数返回窗口的句柄 函数的定义:HWND WINAPI FindWindow(LPCSTR lpClassName ,LPCST ...
- python操作三大主流数据库(12)python操作redis的api框架redis-py简单使用
python操作三大主流数据库(12)python操作redis的api框架redis-py简单使用 redispy安装 安装及简单使用:https://github.com/andymccurdy/ ...
- REST API 概念的简单介绍
REST API 概念的简单介绍 最近发现很多人不了解REST是什么,我综合了下网上的文章,摘录了一下. 首先要明确一点:REST(Representational State Transfer,表述 ...
- java api apk_java-如何在APK文件中保护此API
我目前正在为我运营的网站开发API.该API将在许多地方使用,其中一个地方是Android应用程序. 目的是允许用户登录和下载文件.我有api构建,它将使用HTTPS,因此在传输时所有数据都很好. 我 ...
- 使用有道词典API做一个简单的翻译页面 HTML+JS+有道词典API(代码可直接运行)
使用有道词典API做一个简单的翻译页面 HTML+JS+有道词典API(代码可直接运行) 代码很简单,适合初学者学习借鉴.可以当成一个小工具使用. 注意: 1.代码应联网使用. 2.在输入密钥和id时 ...
最新文章
- 赛迪研究院发布《2019量子计算发展白皮书》
- 成功解决 OSError: [WinError 193] %1 不是有效的 Win32 应用程序
- linux cpu占用100原因查询,如何根据查询异常时间节点和连接进而确定CPU使用率100%的原因...
- 优秀项目经理必备的8个要素
- php 处理 mysql to json, 前台js处理
- Json动态添加属性
- SQLi LABS Less 25a 联合注入+布尔盲注+时间盲注
- javascript设计模式-抽象工厂模式
- 转: gob编解码
- win10 计算机 权限,介绍电脑windows10管理员权限开启的4种方法
- 系统电脑频繁假死(突然屏幕卡死,任何操作无法执行)
- 计算机组装物料清单,物料清单
- 已知直线上两点求直线的一般式方程
- Google Go 语言从入门到应用必备开源项目
- layui表格时间格式化
- 十年磨一剑,今日把示君:架构师分享从一名码农到如今的成长经验
- ps切图技巧、基础工具,使用方法总结
- Skywalking链路追踪自身耗时和总耗时算法分析
- Monotonic Matrix(lemma定理)
- Unity3d 数字模型制作规范
热门文章
- Kenney Assets - 提供数以万计免费商用的游戏制作素材下载,包括 2d、3d素材,游戏音效和游戏 UI
- word中给多个字符添加圆圈
- 香橙派OrangePi Zero2开发板外接USB无线网卡测试示例
- linux批量文件处理,Linux一行命令处理批量文件
- 关于yolov5进行FPS游戏的目标检测,实现自动瞄准。
- 树莓派3配置局域网打印服务器惠普打印机HP1108
- php初中历史专题教学网站 毕业设计源码100623
- 浅谈ASCII码、unicode码等
- java进度条动画_Android自定义控件之圆形进度条动画
- From .1:从屏保到Win平台开发 - 一个可运行的C#屏保程序