安全上

我刚刚宣布了新消息学习Spring Security课程,包括全部材料专注于春季安全的新OAuth2堆栈5:

>>看看这个课程

1.概述

在本教程中,我们将使用OAuth2确保REST API,并从一个简单的角度客户端使用它。

我们要构建的应用程序将包括三个单独的模块:

  • 授权服务器
  • 资源服务器
  • UI授权代码:使用授权代码流的前端应用程序

我们将在Spring Security 5中使用OAuth堆栈。如果要使用Spring Security OAuth遗留堆栈,请查看此前的文章:Spring REST API + OAuth2 + Angular(使用Spring Security OAuth遗留堆栈)

进一步阅读:

使用JWT与Spring Security OAuth

使用Spring Security 5使用JWT令牌的指南。

OAuth2.0和动态客户端注册(使用Spring Security OAuth遗留堆栈)

了解如何使用Spring Security和OAuth2动态定义客户端。

让我们跳进去。

2. OAuth2授权服务器(AS)

简单地说,授权服务器是发布授权令牌的应用程序。

以前,Spring Security OAuth堆栈提供了将授权服务器设置为Spring应用程序的可能性。但是这个项目已经被弃用了,主要是因为OAuth是一个开放的标准,有许多成熟的提供商,比如Okta、Keycloak和ForgeRock,举几个例子。

其中,我们将使用Keycloak.。它是一个由Red Hat管理的开源身份和访问管理服务器,由JBoss在Java中开发。它不仅支持OAuth2,还支持OpenID Connect和SAML等其他标准协议。

对于本教程,我们将设置一个嵌入式keycloak服务器在Spring Boot应用程序中

3.资源服务器(RS)

现在让我们讨论资源服务器;这本质上是REST API,我们最终希望能够使用它。

3.1。Maven配置

我们的资源服务器的pom与之前的授权服务器的pom基本相同,没有Keycloak部分和一个额外的spring-boot-starter-oauth2-resource-server依赖性:

<依赖项>  org.springframework.boot   Spring-Boot-Starter-OAuth2-Resource-Server  

3.2。安全配置

因为我们用的是Spring Boot,我们可以使用Boot属性定义所需的最小配置。

我们用application.yml文件:

Server: port: 8081 servlet: context-path: /resource-server spring: security: oauth2: resourceserver: jwt: issuer-uri: http://localhost:8083/auth/realms/金宝搏188体育baeldung jwk-set-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/certs

这里,我们指定将使用JWT令牌进行授权。

JWK-Set-URI属性指向包含公钥的URI,以便我们的资源服务器可以验证令牌的完整性。

发行人 - URI.属性表示验证令牌颁发者(即授权服务器)的额外安全措施。但是,添加此属性还要求在启动Resource Server应用程序之前应该运行Authorization Server。

接下来,我们建立aAPI的安全配置,以保护端点:

@configuration公共类securityconfig扩展WebSecurityConfigurerAdapter {@override保护的void配置(Httpsecurity HTTP)抛出异常{http.cors().and().AuthorizeRequests().antmatchers(httpmethod.get,“/ info”,“/ api/ foos / **“).hasauthority(”scope_read“).antmatchers(httpmethod.post,”/ api / foos“).hasauthority(”scope_write“).AnyRequest().authenticated().And().oauth2resourceerver().jwt();}}

正如我们所看到的,对于GET方法,我们只允许具有范围。对于POST方法,请求者需要具有权威。但是,对于任何其他端点,请求应该只对任何用户进行身份验证。

还,oauth2ResourceServer ()方法指定这是一个资源服务器,使用智威汤逊()-格式化的令牌。

这里的另一个点是使用方法的使用歌珥()允许请求上的Access-Control头。这一点尤其重要,因为我们要处理的是Angular客户端,而我们的请求将来自另一个源URL。

3.4。模型和存储库

接下来,我们来定义ajavax.persistence.entity.为我们的模型,Foo:

@Entity public class Foo {@Id @GeneratedValue(策略= GenerationType.IDENTITY) private Long id;私人字符串名称;//构造函数}

然后我们需要一个存储库Foo我们将使用Spring’sPagingAndSortingRepository:

IFooRepository extends PagingAndSortingRepository {}

3.4。服务和实施

之后,我们将为我们的API定义和实现一个简单的服务:

public interface IFooService{可选 findById(Long id);Foo保存(Foo Foo);Iterable < Foo > findAll ();} @Service public class FooServiceImpl implements IFooService {private IFooRepository fooRepository;public FooServiceImpl(IFooRepository fooRepository){这个。fooRepository = fooRepository;} @Override public可选 findById(长id){返回fooRepository.findById(id);} @Override public Foo save(Foo Foo){返回fooRepository.save(Foo);} @Override public Iterable findAll(){返回fooRepository.findAll();}}

3.5。一个示例控制器

现在让我们实现一个简单的控制器Foo资源通过DTO:

@RestController @RequestMapping(value = "/api/foo ") public class FooController {private IFooService fooService;public FooController(IFooService fooService) {this。fooService = fooService;} @CrossOrigin(origins = "http://localhost:8089") @GetMapping(value = "/{id}") public FooDto findOne(@PathVariable Long id) {Foo entity = fooService.findById(id) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));返回convertToDto(实体);} @GetMapping public Collection findAll() {Iterable Foo = this.fooService.findAll();List fooDtos = new ArrayList<>();foo。forEach (p - > fooDtos.add (convertToDto (p)));返回fooDtos;}保护FooDto convertToDto(Foo实体){FooDto dto = new FooDto(entity. getid (), entity. getname ()); return dto; } }

注意使用@CrossOrigin以上;这是控制器级配置,我们需要允许从指定的URL运行的Angular应用程序中的CORS。

这是我们的FooDto:

public class FooDto {private long id;私人字符串名称;}

4.前端-设置

现在我们来看一个简单的客户端Angular前端实现,它将访问我们的REST API。

我们第一次使用角CLI生成和管理我们的前端模块。

首先,我们安装节点和NPM.,因为Angular CLI是一个npm工具。

然后我们需要使用Frontend-Maven-Plugin使用Maven构建我们的角度项目:

   com.github。eirslett < / groupId > < artifactId > frontend-maven-plugin < / artifactId > <版本> 1.3 < /版本> <配置> < nodeVersion > v6.10.2 < / nodeVersion > < npmVersion > 3.10.10 < / npmVersion > < workingDirectory > src / main /资源< / workingDirectory > < /配置> <执行> <执行> < id >安装节点和npm < / id > <目标> <目标> install-node-and-npm > < /目标   npm install  npm    npm run build  npm   run build      

最后,使用Angular CLI生成新模块:

ng新oauthApp

在以下部分中,我们将讨论Angular App Logic。

5.使用角度授权码流

我们将在这里使用OAuth2授权代码流。

我们的用例:客户机应用程序从授权服务器请求一个代码,并显示一个登录页面。一旦用户提供有效的凭据和提交,授权服务器就会给我们代码。然后前端客户端使用它来获取访问令牌。

5.1。家组件

让我们从我们的主要组成部分开始HomeComponent所有的动作都是从这里开始的:

@component({选择器:'家庭标题',提供程序:[appservice],模板:` <按钮* ngif =“!isloggedin”class =“btn btn-miginer”(单击)=“登录()”类型=“提交”>登录 
欢迎!! logout
`})导出类HomeComponent {public iSloggedIn = false;构造函数(私有_service:appservice){} ngoninit(){this.isloggingin = this._service.checkcredentials();让i = window.location.href.indexof('代码');如果(!这个!它.Isloggedin && i!= -1){this._service.rerefetoken(window.location.href.substring(i + 5));}} login(){window.location.href ='http:// localhost:8083 / auth / realms金宝搏188体育 / baeldung / protocol / Openid-Connect / auth?response_type = code&scope = OpenID%20Write%20read&client_id ='+ this._service.clientid +'&redirect_uri ='+ this._service.redirecturi;} logout(){this._service.logout();}}

一开始,当用户未登录时,只会出现登录按钮。单击此按钮后,用户将导航到应用服务器的授权URL,在那里输入用户名和密码。成功登录后,用户将使用授权代码重定向回来,然后我们使用此代码检索访问令牌。

5.2。应用服务

现在让我们看看AppService——位于app.service.ts-包含服务器交互的逻辑:

  • RetreveToken():使用授权码获取访问令牌
  • saveToken ():使用ng2-cookie库将我们的访问令牌保存在一个cookie中
  • getResource ():从服务器获取一个Foo对象使用它的ID
  • checkCredentials ():检查用户是否登录
  • 登出():删除访问令牌cookie并记录用户
出口类Foo {构造(公共ID:号码,公共名称:串){}} @Injectable()出口类AppService服务{公共的clientId = 'newClient';public redirecturi ='http:// localhost:8089 /';构造函数(私有_http:httpclient){} RetreveToken(代码){让params = new urlsearchparams();params.append('grant_type','authorization_code');params.append('client_id',this.clientid);params.append('client_secret','newclienticre');params.append('redirect_uri',this.redirecturi);params.append('代码',代码);让标题=新的httpheaders({'内容类型':'application / x-www-form-ullencoded; charset = utf-8'});this._http.post('http:// localhost:8083 / auth / re金宝搏188体育almms / baeldung / protocol / Openid-connect / token',params.tostring(),{标题:标题}).subscribe(data => this。SaveToken(数据),Err =>警报('无效凭据')); } saveToken(token) { var expireDate = new Date().getTime() + (1000 * token.expires_in); Cookie.set("access_token", token.access_token, expireDate); console.log('Obtained Access token'); window.location.href = 'http://localhost:8089'; } getResource(resourceUrl) : Observable { var headers = new HttpHeaders({ 'Content-type': 'application/x-www-form-urlencoded; charset=utf-8', 'Authorization': 'Bearer '+Cookie.get('access_token')}); return this._http.get(resourceUrl, { headers: headers }) .catch((error:any) => Observable.throw(error.json().error || 'Server error')); } checkCredentials() { return Cookie.check('access_token'); } logout() { Cookie.delete('access_token'); window.location.reload(); } }

retrieveToken方法,我们使用客户机凭据和Basic Auth发送帖子到了/ openid-connect /令牌端点获取访问令牌。参数以url编码的格式发送。获得访问令牌后,我们把它储存在饼干里。

cookie存储在这里特别重要,因为我们只将cookie用于存储目的,而不是直接驱动身份验证过程。这有助于防止跨站点请求伪造(CSRF)攻击和漏洞。

5.3。Foo组件

最后,我们FooComponent显示Foo的详细信息:

@Component({selector: ' Foo - Details ', providers: [AppService], template: ' 

Foo Details

{{Foo。
'}) export class FooComponent {public Foo = New Foo(1,'sample Foo ');private foosUrl = 'http://localhost:8081/resource-server/api/foos/';constructor(private _service:AppService) {} getFoo() {this._service. getresource (this. foosurl +this.foo.id) .subscribe(data => this.foo.id);foo = data, error => this.foo.name = ' error ');}}

5.5。应用程序组件

我们的简单AppComponent充当根成分:

@Component({selector: 'app-root', template: '   '}) export class AppComponent {}

AppModule我们将所有组件、服务和路由打包在这里:

@NgModule({declarations: [AppComponent, HomeComponent, FooComponent], imports: [BrowserModule, HttpClientModule, RouterModule.]forRoot([{path: ", component: HomeComponent, pathMatch: 'full'}], {onSameUrlNavigation: 'reload'})], providers: [], bootstrap: [AppComponent]})导出类AppModule {}

7.运行前端

1.要运行任何前端模块,我们需要先构建应用:

MVN清洁安装

2.然后我们需要导航到Angular应用的目录:

CD SRC /主/资源

3.最后,我们将启动我们的应用程序:

npm开始

默认情况下,服务器将在端口4200上启动;要更改任何模块的端口,请更改:

“开始”:“ng”服务

package.json;例如,要在端口8089上运行,请添加:

"start": "ng serve——port 8089"

8.结论

在本文中,我们学习了如何使用OAuth2对应用程序进行授权。

可以找到本教程的全面实现GitHub项目

安全底部

我刚刚宣布了新消息学习Spring Security课程,包括全部材料专注于春季安全的新OAuth2堆栈5:

>>看看这个课程
83.评论
最老的
最新
内联反馈
查看所有评论
评论在本文上关闭!