javascript
Angular5 JWT身份验证(Spring Boot安全性)
歡迎使用帶有Spring Security的angular5 jwt身份驗證。在本教程中,我們將在一個angular5單頁應用程序中使用jwt身份驗證創建一個完整的堆棧應用程序,該應用程序具有由spring boot支持并集成了spring security的后備服務器。帶有集成了HttpInterceptor的示例angular5示例應用程序,以攔截所有HTTP請求以在標頭中添加jwt授權令牌,并且在服務器中,我們將使用Spring安全性公開和保護一些REST端點。僅當有效的jwt令牌有效時,該資源才能訪問在標題中找到。我們將使用Mysql DB進行持久性存儲。
本文由4個部分組成。在第一部分中,我們將使用材料設計來構建單頁angular5應用程序。 在第二部分中,我們將創建一個帶有示例REST端點公開的Spring Boot應用程序。 在第三部分中,我們將通過Spring Security與JWT集成,在第四部分中,將使用HttpIntrceptor與angular5進行jwt集成。
使用的技術
我們在角度和Spring啟動中都進行了頻繁的版本升級。 因此,讓我們首先確認將用于構建此應用程序的這些技術的版本。
1. Spring Boot 1.5.8.RELEASE
2. jjwt 0.6.0
3.角5.2.0
4.角材料5.1.0
5. MySQL
6. Java 1.8
Jwt認證
JSON Web令牌(JWT)是一個開放標準(RFC 7519),它定義了一種緊湊且自包含的方式來作為JSON對象在各方之間安全地傳輸信息。無狀態身份驗證機制,因為用戶狀態永遠不會保存在服務器內存中。 JWT令牌由三部分組成,并用點號(。)分隔,即Header.payload.signature
標頭使用了兩種部分的令牌和哈希算法。組成這兩個密鑰的JSON結構是Base64Encoded。
{"alg": "HS256","typ": "JWT" }有效負載包含索賠。從根本上講,索賠有三種類型:預留索賠,公共索賠和私人索賠。 保留的聲明是預定義的聲明,例如iss(發布者),exp(到期時間),sub(主題),aud(聽眾)。在私人聲明中,我們可以創建一些自定義聲明,例如主題,角色等。
{"sub": "Alex123","scopes": [{"authority": "ROLE_ADMIN"}],"iss": "http://devglan.com","iat": 1508607322,"exp": 1508625322 }簽名可確保令牌不會在途中發生更改。例如,如果您想使用HMAC SHA256算法,則將通過以下方式創建簽名:
HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)服務器的受保護路由將在Authorization標頭中檢查有效的JWT,如果存在該JWT,則將允許用戶訪問受保護的資源。每當用戶要訪問受保護的路由或資源時,用戶代理都應發送JWT,通常在使用Bearer模式的Authorization標頭中。 標頭的內容應如下所示:
Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJBbGV4MTIzIiwic2N.v9A80eU1VDo2Mm9UqN2FyEpyT79IUmhg創建Angular5應用程序
我們已經在上一篇文章Angular5 Material App中創建了angular5應用程序 。這是一個非常簡單的集成了angular material的應用程序 。在此應用程序中,我們集成了2個模塊用戶和具有路由的登錄模塊。但是這里的登錄驗證是在客戶端應用程序本身中進行了硬編碼,一旦用戶成功登錄,他將被重定向到用戶頁面,在該頁面上他可以看到數據表中的用戶列表。
以下是先前的項目結構以及我們現在將要構建的項目結構。
在這個例子中,我們需要首先創建一個使用REST API的HTTP客戶端,為此我們將使用@angular/common/http HttpClient。 以下是我們的app.service.ts 。 出于演示目的,我們只有一個HTTP調用來獲取用戶列表。
import {Injectable} from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import {Observable} from 'rxjs/Observable'; import {User} from './user/user.model';const httpOptions = {headers: new HttpHeaders({ 'Content-Type': 'application/json' }) };@Injectable() export class UserService {constructor(private http: HttpClient) {}private userUrl = 'http://localhost:8080/';public getUsers(): Observable {return this.http.get(this.userUrl + '/users');}}不要忘記在app.module.ts的提供程序中包含userService和HttpClientModule
同樣,在user.component.ts我們對服務進行了以下更改并填充了數據表。還要注意的一件事是,我們在spring安全配置中禁用了/ users端點的身份驗證。
import {Component, OnInit} from '@angular/core'; import {MatTableDataSource} from '@angular/material'; import {User} from './user.model'; import {UserService} from '../app.service'; import {Router} from '@angular/router';@Component({selector: 'app-root',templateUrl: './user.component.html',styleUrls: ['./user.component.css'] }) export class UserComponent implements OnInit {displayedColumns = ['id', 'username', 'salary', 'age'];dataSource = new MatTableDataSource();constructor(private router: Router, private userService: UserService) {}ngOnInit(): void {this.userService.getUsers().subscribe(data => {this.dataSource.data = data;});} }現在,有了這樣的實現,我們應該能夠在URL的數據表中顯示用戶列表– http:// localhost:4200 / user
創建Spring Boot應用程序
首先,檢查以下項目結構。 這是我們在spring boot jwt認證教程期間構建的項目。
Spring Boot應用程序的端點在控制器類的/ users中公開。 這是一個簡單的實現,此外,我們為angular啟用了CORS,并且用戶模型類具有4個屬性,如id,用戶名,年齡和薪水。
UserController.java
package com.devglan.controller;import com.devglan.model.User; import com.devglan.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*;import java.util.List;@CrossOrigin(origins = "http://localhost:4200", maxAge = 3600) @RestController public class UserController {@Autowiredprivate UserService userService;@RequestMapping(value="/users", method = RequestMethod.GET)public List listUser(){return userService.findAll();} }Spring安全配置
現在,我們將配置安全性以保護我們的應用程序。 現在,我們將允許/users端點進行公共訪問,以便稍后可以驗證jwt身份驗證并在數據表中顯示用戶列表(如上圖所示)。所有這些配置已在我的上一篇文章中討論過-Spring Boot Security JWT身份驗證。這里authenticationTokenFilterBean()無效,因為我們允許/users端點進行公共訪問。
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Resource(name = "userService")private UserDetailsService userDetailsService;@Autowiredprivate JwtAuthenticationEntryPoint unauthorizedHandler;@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Autowiredpublic void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService).passwordEncoder(encoder());}@Beanpublic JwtAuthenticationFilter authenticationTokenFilterBean() throws Exception {return new JwtAuthenticationFilter();}@Overrideprotected void configure(HttpSecurity http) throws Exception {http.cors().and().csrf().disable().authorizeRequests().antMatchers("/token/*").permitAll().anyRequest().authenticated().and().exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);http.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);}@Beanpublic BCryptPasswordEncoder encoder(){return new BCryptPasswordEncoder();}}在Spring Security中添加JWT身份驗證
JWT的簡單實現是編寫一個過濾器類,該類將攔截所有請求并查找JWT授權令牌,如果在標頭中找到該令牌,它將提取該令牌,解析該令牌以查找與用戶相關的信息,例如username令牌被驗證后,它將準備spring安全上下文,并將請求轉發到過濾器鏈中的下一個過濾器。
因此,為此目的,我們提供了OncePerRequestFilter類,該類每個請求執行一次。 在過濾器中,我們正在對角色進行硬編碼,但在實時應用程序中,我們可以從JWT令牌的自定義范圍中提取它,或者在UserDetailsService進行數據庫查找
JwtAuthenticationFilter.java
public class JwtAuthenticationFilter extends OncePerRequestFilter {@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Overrideprotected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain) throws IOException, ServletException {String header = req.getHeader(HEADER_STRING);String username = null;String authToken = null;if (header != null && header.startsWith(TOKEN_PREFIX)) {authToken = header.replace(TOKEN_PREFIX,"");try {username = jwtTokenUtil.getUsernameFromToken(authToken);} catch (IllegalArgumentException e) {logger.error("an error occured during getting username from token", e);} catch (ExpiredJwtException e) {logger.warn("the token is expired and not valid anymore", e);} catch(SignatureException e){logger.error("Authentication Failed. Username or Password not valid.");}} else {logger.warn("couldn't find bearer string, will ignore the header");}if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {UserDetails userDetails = userDetailsService.loadUserByUsername(username);if (jwtTokenUtil.validateToken(authToken, userDetails)) {UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")));authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));logger.info("authenticated user " + username + ", setting security context");SecurityContextHolder.getContext().setAuthentication(authentication);}}chain.doFilter(req, res);} }完成此實現后,我們實際上可以從WebSecurityConfig.java刪除“ / users”并驗證在嘗試將數據加載到角度數據表中時將得到401。
在Spring Security中創建JWT令牌
我們定義了這個控制器來生成JWT令牌。該控制器will方法將在登錄請求期間從客戶端調用。 它將使用用戶名和密碼組合從數據庫驗證用戶,并相應地生成JWT令牌。
AuthenticationController.java
@RestController @RequestMapping("/token") public class AuthenticationController {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Autowiredprivate UserService userService;@RequestMapping(value = "/generate-token", method = RequestMethod.POST)public ResponseEntity register(@RequestBody LoginUser loginUser) throws AuthenticationException {final Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(loginUser.getUsername(),loginUser.getPassword()));SecurityContextHolder.getContext().setAuthentication(authentication);final User user = userService.findOne(loginUser.getUsername());final String token = jwtTokenUtil.generateToken(user);return ResponseEntity.ok(new AuthToken(token));}}Angular5 JWT授權
現在,當要在帶有Spring Security的angular5中進行JWT授權集成時,首先我們需要發出POST請求以使用用戶名和密碼登錄。 在響應中,服務器將在成功身份驗證后為您提供JWT令牌。一旦獲得此令牌,我們便可以將其緩存在瀏覽器中以供以后的API調用重用。現在讓我們定義我們的authservice,它將在以下位置請求JWT令牌登錄。
驗證服務
import {Injectable} from '@angular/core'; import {Observable} from 'rxjs/Observable'; import { HttpClient, HttpHeaders } from '@angular/common/http';@Injectable() export class AuthService {baseUrl: 'http://localhost:8080/email2sms/';constructor(private http: HttpClient) {}attemptAuth(ussername: string, password: string): Observable {const credentials = {username: ussername, password: password};console.log('attempAuth ::');return this.http.post('http://localhost:8080/token/generate-token', credentials);}}現在,在登錄期間,我們將通過調用spring security AUTH API來調用此服務以對用戶進行身份驗證。
login.component.ts
import { Component, OnInit } from '@angular/core'; import {Router} from '@angular/router'; import {MatDialog} from '@angular/material'; import {AuthService} from '../core/auth.service'; import {TokenStorage} from '../core/token.storage';@Component({selector: 'app-login',templateUrl: './login.component.html',styleUrls: ['./login.component.css'] }) export class LoginComponent {constructor(private router: Router, public dialog: MatDialog, private authService: AuthService, private token: TokenStorage) {}username: string;password: string;login(): void {this.authService.attemptAuth(this.username, this.password).subscribe(data => {this.token.saveToken(data.token);this.router.navigate(['user']);});}}手動在所有API請求的標頭中添加此令牌并不是一種更干凈的方法。 因此,我們將實現一個HTTPInterceptor,它將攔截所有rquest并將此JWT授權令牌添加到標頭中。 此外,我們可以攔截響應,對于任何未經授權的請求或過期的令牌,我們都可以將用戶重定向到登錄頁面。此外,要在本地存儲此令牌,我們可以使用sessionstorage – sessionStorage對象僅存儲一個會話的數據(該數據將被刪除當瀏覽器標簽關閉時)。 攔截器將實現HttpInterceptor接口,并重寫intercept() 。 在這里,我們正在克隆設置所需標題的請求,下面是攔截器的實現。
應用攔截器
import { Injectable } from '@angular/core'; import {HttpInterceptor, HttpRequest, HttpHandler, HttpSentEvent, HttpHeaderResponse, HttpProgressEvent,HttpResponse, HttpUserEvent, HttpErrorResponse} from '@angular/common/http'; import { Observable } from 'rxjs/Observable'; import { Router } from '@angular/router'; import {TokenStorage} from './token.storage'; import 'rxjs/add/operator/do';const TOKEN_HEADER_KEY = 'Authorization';@Injectable() export class Interceptor implements HttpInterceptor {constructor(private token: TokenStorage, private router: Router) { }intercept(req: HttpRequest, next: HttpHandler):Observable | HttpUserEvent> {let authReq = req;if (this.token.getToken() != null) {authReq = req.clone({ headers: req.headers.set(TOKEN_HEADER_KEY, 'Bearer ' + this .token.getToken())});}return next.handle(authReq).do((err: any) => {if (err instanceof HttpErrorResponse) {if (err.status === 401) {this.router.navigate(['user']);}}});}}不要錯過在app.module.ts中注冊該攔截器的機會。
要將此令牌存儲在瀏覽器存儲中,讓我們定義我們的token.storage.ts
import { Injectable } from '@angular/core';const TOKEN_KEY = 'AuthToken';@Injectable() export class TokenStorage {constructor() { }signOut() {window.sessionStorage.removeItem(TOKEN_KEY);window.sessionStorage.clear();}public saveToken(token: string) {window.sessionStorage.removeItem(TOKEN_KEY);window.sessionStorage.setItem(TOKEN_KEY, token);}public getToken(): string {return sessionStorage.getItem(TOKEN_KEY);} }還有客戶端jwt令牌驗證器,我們可以使用它來檢查令牌到期。 這樣做,我們不必依賴服務器來檢查令牌到期。
結論
在本文中,我們了解了如何將JWT令牌與Angular5應用程序集成在一起,并具有支持的Spring Boot安全性。 如果您喜歡這篇文章,我很樂意在評論部分回覆。
翻譯自: https://www.javacodegeeks.com/2018/03/angular5-jwt-authentication-spring-boot-security.html
總結
以上是生活随笔為你收集整理的Angular5 JWT身份验证(Spring Boot安全性)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 旃檀功德佛怎么读 旃檀功德佛
- 下一篇: 周鸿祎:大模型自身不是壁垒,挑战在于垂直