這篇文章主要介紹了spring security和jwt整合的方法是什么的相關(guān)知識,內(nèi)容詳細易懂,操作簡單快捷,具有一定借鑒價值,相信大家閱讀完這篇spring security和jwt整合的方法是什么文章都會有所收獲,下面我們一起來看看吧。

創(chuàng)新互聯(lián)建站主要從事成都網(wǎng)站制作、成都做網(wǎng)站、網(wǎng)頁設計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務。立足成都服務共青城,十多年網(wǎng)站建設經(jīng)驗,價格優(yōu)惠、服務專業(yè),歡迎來電咨詢建站服務:13518219792
json web token (JWT),是為了在網(wǎng)絡環(huán)境中傳遞聲明而設計的一種基于JSON的開放標準(RFC 7519),該token 被設計為緊湊且安全的.特別使用于分布式站點的登陸(SSO)
場景.JWT一般被用來在服務提供者和服務認證者之間傳遞身份信息,以便可以從服務器獲取資源.也可以增加一些額外的其它業(yè)務邏輯所必需的聲明信息.
該token可直接被用于認證,也可用于被加密.
基于token的鑒權(quán)機制也是類似于http協(xié)議無狀態(tài)的,它不需要在服務段保留用戶的認證信息或者鑒權(quán)信息.這就意味著基于token認證機制的用戶就不必考慮在哪一臺服務器登錄了.
這就為應用的擴展提供了遍歷.
認證流程:
這個token必須在每次請求時傳遞給服務端,它應該保存在請求頭里面.另外,服務器端要支持 CORS(跨來源資源共享策略) ,一般我們在服務器上這么做就可以了, Access-Control-Allow-Origin: *
jwt的三個組成部分共同構(gòu)成了一個 簽名信息 signature
**這個部分需要base64加密后的header和base64加密后的payload使用.連接組成的字符串.
然后通過header中聲明的加密方式進行加鹽secret組合加密,然后就構(gòu)成了jwt的第三部分。**
注意:secret是保存在服務器端的,jwt的簽發(fā)生成也是在服務器端的,secret就是用來進行jwt的簽發(fā)和jwt的驗證,
所以,它就是你服務端的私鑰,在任何場景都不應該流露出去。一旦客戶端得知這個secret, 那就意味著客戶端是可以自我簽發(fā)jwt了。
一般是在請求頭里加入Authorization,并加上Bearer標注:如下:
fetch('api/user/1', {
headers: {
'Authorization': 'Bearer '我們之前介紹過,Spring security是基于過濾器(Filter)的,使用過濾器我們可以很容易的攔截某些請求.
因此通過上面對jwt的了解,我們就可以在過濾器中處理token的生成和校驗.
大致流程如下:
1.當用戶進行提交登陸表單時,自定義一個攔截器JWTLoginFilter進行表單參數(shù)的獲取.
2.驗證提交的用戶名密碼是否正確.
3.如果登陸成功,使用jwt頒發(fā)一個token給客戶端,之后的客戶端請求都要帶上這個token.
4.token驗證:再自定義一個過濾器JWTAuthenticationFilter,當用戶訪問需要認證的請求時,攔截該請求,并進行token校驗.
我們?yōu)榱撕喕_發(fā)使用spring boot進行項目的快速搭建.需要引入如下依賴:
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies>
之后我們創(chuàng)建一個controller進行不同級別的驗證.
/**
* @author itguang
* @create
@RestController
public class UserController
@Autowired
private UserRepository applicationUserRepository;
@RequestMapping("/hello")
public String hello(){
return "hello";
}
@RequestMapping("/userList")
public Map<String, Object> userList(){
List<User> myUsers = applicationUserRepository.findAll();
Map<String, Object> map = new HashMap<String, Object>();
map.put("users",myUsers);
return map;
}
@RequestMapping("/admin")
public String admin(){
return "admin";
}
}接下來就是配置我們的安全管理類 SecurityConfig :
/**
* @author itguang
* @create
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter
@Autowired
private BCryptPasswordEncoder bCryptPasswordEncoder;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
// 使用自定義身份驗證組件
auth.authenticationProvider(new CustomAuthenticationProvider(userDetailsService,bCryptPasswordEncoder));
}
@Override
protected void configure(HttpSecurity http) throws Exception {
//禁用 csrf
http.cors().and().csrf().disable().authorizeRequests()
//允許以下請求
.antMatchers("/hello").permitAll()
// 所有請求需要身份認證
.anyRequest().authenticated()
.and()
//驗證登陸
.addFilter(new JWTLoginFilter(authenticationManager()))
//驗證token
.addFilter(new可以看到我們的Security繼承了 WebSecurityConfigurerAdapter ,關(guān)于WebSecurityConfigurerAdapter我們之前的文章已經(jīng)介紹過,
我們重點關(guān)注的是重載的兩個 configure() 方法.
configure(HttpSecurity http):這個方法配置了對請求的攔截配置,在這里我們又添加了兩個自定義的過濾器,JWTLoginFilter 和JWTAuthenticationFilter,
分別負責登錄時用戶名密碼的驗證,和攔截請求時對token的驗證.
configure(AuthenticationManagerBuilder auth):這個方法有點奇怪,我們并沒有使用之前介紹幾種的用戶存儲,而是使用了一個authenticationProvider()
方法,并傳入了一個我們自定義的 AuthenticationProvider 類型的對象作為參數(shù).稍后我們會詳細介紹這個類到底是什么.
/**
* @author itguang
* @create
public class JWTLoginFilter extends UsernamePasswordAuthenticationFilter
private AuthenticationManager authenticationManager;
public JWTLoginFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
/**
* 接收并解析用戶登陸信息 /login,
*為已驗證的用戶返回一個已填充的身份驗證令牌,表示成功的身份驗證
*返回null,表明身份驗證過程仍在進行中。在返回之前,實現(xiàn)應該執(zhí)行完成該過程所需的任何額外工作。
*如果身份驗證過程失敗,就拋出一個AuthenticationException
*
*
* @param request 從中提取參數(shù)并執(zhí)行身份驗證
* @param response 如果實現(xiàn)必須作為多級身份驗證過程的一部分(比如OpenID)進行重定向,則可能需要響應
* @return 身份驗證的用戶令牌,如果身份驗證不完整,則為null。
* @throws
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
//得到用戶登陸信息,并封裝到 Authentication 中,供自定義用戶組件使用.
String username = request.getParameter("username");
String password = request.getParameter("password");
if (username == null) {
username = "";
}
if (password == null) {
password = "";
}
username = username.trim();
ArrayList<GrantedAuthorityImpl> authorities = new ArrayList<>();
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password, authorities);
//authenticate()接受一個token參數(shù),返回一個完全經(jīng)過身份驗證的對象,包括證書.
// 這里并沒有對用戶名密碼進行驗證,而是使用 AuthenticationProvider 提供的 authenticate 方法返回一個完全經(jīng)過身份驗證的對象,包括證書.
// Authentication authenticate = authenticationManager.authenticate(authenticationToken);
//UsernamePasswordAuthenticationToken 是 Authentication 的實現(xiàn)類
return authenticationToken;
}
/**
* 登陸成功后,此方法會被調(diào)用,因此我們可以在次方法中生成token,并返回給客戶端
*
* @param request
* @param response
* @param chain
* @param
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain, Authentication authResult) {
String token = Jwts.builder()
.setSubject(authResult.getName())
//有效期兩小時
.setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 2 * 1000))
//采用什么算法是可以自己選擇的,不一定非要采用HS512
.signWith(SignatureAlgorithm.HS512, "MyJwtSecret")
.compact();
response.addHeader("token", "Bearer "我們可以看到 JWTLoginFilter 繼承了 UsernamePasswordAuthenticationFilter,
并且重寫了它的 attemptAuthentication() 方法和 successfulAuthentication() 方法.
在 attemptAuthentication()方法中,我們就可以得到 /login 提交的用戶名和密碼信息,但這里我們并沒有返回一個認證后的 Authentication,
這是為什么呢?原因就在于,我們在 SecurityConfigure 的方法中,使用了一個自定義的 AuthenticationProvider 實現(xiàn)類,如:
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
// 使用自定義身份驗證組件
auth.authenticationProvider(new那么 AuthenticationProvider 用來干嘛的呢? 查看他的源碼可以發(fā)現(xiàn):
public interface AuthenticationProvider /** * 驗證登錄信息,若登陸成功,設置 Authentication * * @param authentication * @return 一個完全經(jīng)過身份驗證的對象,包括憑證。 * 如果AuthenticationProvider無法支持已通過的身份驗證對象的身份驗證,則可能返回null。 * 在這種情況下,將會嘗試支持下一個身份驗證類的驗證提供者。 * @throws Authentication authenticate(Authentication authentication) throws AuthenticationException; /** * 是否可以提供輸入類型的認證服務 * * 如果這個AuthenticationProvider支持指定的身份驗證對象,那么返回true。 * 返回true并不能保證身份驗證提供者能夠?qū)ι矸蒡炞C類的實例進行身份驗證。 * 它只是表明它可以支持對它進行更深入的評估。身份驗證提供者仍然可以從身份驗證(身份驗證)方法返回null, * 以表明應該嘗試另一個身份驗證提供者。在運行時管理器的運行時,可以選擇具有執(zhí)行身份驗證的身份驗證提供者。 * * @param authentication * @return boolean
AuthenticationProvider(身份驗證提供者) 顧名思義,可以提供一個 Authentication 供Spring Security的上下文使用.
通過 supports 方法我們對特定的 Authentication進行認證,如果返回 true,就交給 authenticate(Authentication authentication) 方法,
此方法一個完全經(jīng)過身份驗證的對象,包括憑證。
如下我們自定義的 CustomAuthenticationProvider:
/**
* AuthenticationProvider(身份驗證提供者) 顧名思義,可以提供一個 Authentication 供Spring Security的上下文使用,
*
* @author itguang
* @create
public class CustomAuthenticationProvider implements AuthenticationProvider
private UserDetailsService userDetailsService;
private BCryptPasswordEncoder bCryptPasswordEncoder;
public CustomAuthenticationProvider(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
this.userDetailsService = userDetailsService;
this.bCryptPasswordEncoder = bCryptPasswordEncoder;
}
/**
* 是否可以提供輸入類型的認證服務
* <p>
* 如果這個AuthenticationProvider支持指定的身份驗證對象,那么返回true。
* 返回true并不能保證身份驗證提供者能夠?qū)ι矸蒡炞C類的實例進行身份驗證。
* 它只是表明它可以支持對它進行更深入的評估。身份驗證提供者仍然可以從身份驗證(身份驗證)方法返回null,
* 以表明應該嘗試另一個身份驗證提供者。在運行時管理器的運行時,可以選擇具有執(zhí)行身份驗證的身份驗證提供者。
*
* @param authentication
* @return
@Override
public boolean supports(Class<?> authentication) {
return authentication.equals(UsernamePasswordAuthenticationToken.class);
}
/**
* 驗證登錄信息,若登陸成功,設置 Authentication
*
* @param authentication
* @return 一個完全經(jīng)過身份驗證的對象,包括憑證。
* 如果AuthenticationProvider無法支持已通過的身份驗證對象的身份驗證,則可能返回null。
* 在這種情況下,將會嘗試支持下一個身份驗證類的驗證提供者。
* @throws
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 獲取認證的用戶名 & 密碼
String username = authentication.getName();
String password = authentication.getCredentials().toString();
//通過用戶名從數(shù)據(jù)庫中查詢該用戶
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
//判斷密碼(這里是md5加密方式)是否正確
String dbPassword = userDetails.getPassword();
String encoderPassword = DigestUtils.md5DigestAsHex(password.getBytes());
if (!dbPassword.equals(encoderPassword)) {
throw new UsernameIsExitedException("密碼錯誤");
}
// 還可以從數(shù)據(jù)庫中查出該用戶所擁有的權(quán)限,設置到 authorities 中去,這里模擬數(shù)據(jù)庫查詢.
ArrayList<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new GrantedAuthorityImpl("ADMIN"));
Authentication auth = new UsernamePasswordAuthenticationToken(username, password, authorities);
return可見我們在這個 AuthenticationProvider 中對 UsernamePasswordAuthenticationToken 進行認證,
在 authenticate(Authentication authentication)方法中, authentication 就是 我們之前返回的 UsernamePasswordAuthenticationToken,我們可以得到登陸的用戶名和密碼,進行真正的認證.
如果認證成功 就給改 UsernamePasswordAuthenticationToken 設置對應的權(quán)限,最后把已經(jīng)認證的 UsernamePasswordAuthenticationToken 返回即可.
還有我們在通過用戶名從數(shù)據(jù)庫查找用戶時,返回了一個 UserDetails 對象,關(guān)于UserdDetails對象,我們之前的文章已經(jīng)介紹過,不懂得可以去查看一下.
最后,當 CustomAuthenticationProvider 認證成功之后,JWTLoginFilter 中的 successfulAuthentication() 方法機會執(zhí)行,因此我們就可以在這里設置token了,如下:
/**
* 登陸成功后,此方法會被調(diào)用,因此我們可以在次方法中生成token,并返回給客戶端
*
* @param request
* @param response
* @param chain
* @param
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain, Authentication authResult) {
String token = Jwts.builder()
.setSubject(authResult.getName())
//有效期兩小時
.setExpiration(new Date(System.currentTimeMillis() + 60 * 60 * 2 * 1000))
//采用什么算法是可以自己選擇的,不一定非要采用HS512
.signWith(SignatureAlgorithm.HS512, "MyJwtSecret")
.compact();
response.addHeader("token", "Bearer "我們使用JWT構(gòu)造了一個token字符串,并把它放在了http請求頭中返回給了客戶端.
至此我們的登陸認證并返回 token就已經(jīng)完成了,接下來就是客戶端攜帶這已經(jīng)獲得token訪問需要認證的資源時,我們需要對改token進行驗證了.
/**
* token校驗
*
* @author itguang
* @create
public class JWTAuthenticationFilter extends BasicAuthenticationFilter
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
/**
* 在此方法中檢驗客戶端請求頭中的token,
* 如果存在并合法,就把token中的信息封裝到 Authentication 類型的對象中,
* 最后使用 SecurityContextHolder.getContext().setAuthentication(authentication); 改變或刪除當前已經(jīng)驗證的 pricipal
*
* @param request
* @param response
* @param chain
* @throws IOException
* @throws
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String token = request.getHeader("token");
//判斷是否有token
if (token == null || !token.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
UsernamePasswordAuthenticationToken authenticationToken = getAuthentication(token);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
//放行
chain.doFilter(request, response);
}
/**
* 解析token中的信息,并判斷是否過期
*/
private UsernamePasswordAuthenticationToken getAuthentication(String token) {
Claims claims = Jwts.parser().setSigningKey("MyJwtSecret")
.parseClaimsJws(token.replace("Bearer ", ""))
.getBody();
//得到用戶名
String username = claims.getSubject();
//得到過期時間
Date expiration = claims.getExpiration();
//判斷是否過期
Date now = new Date();
if (now.getTime() > expiration.getTime()) {
throw new UsernameIsExitedException("該賬號已過期,請重新登陸");
}
if (username != null) {
return new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
}
return null;
}
}由此可以看到 JWTAuthenticationFilter 繼承了 BasicAuthenticationFilter,
BasicAuthenticationFilter 用來處理一個HTTP請求的基本授權(quán)標頭,將結(jié)果放入安全上下文。
總之,這個過濾器負責處理任何具有HTTP請求頭的請求的請求,以及一個基本的身份驗證方案和一個base64編碼的用戶名:密碼令牌。
如果身份驗證成功,那么最終的身份驗證對象將被放入安全上下文。
因此我們就可以繼承 BasicAuthenticationFilter 并重寫 doFilterInternal()方法,在該方法中進行token的驗證,如果驗證成功,將結(jié)果放入安全上下文,如:
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
到此,我們就使用Spring Security + JWT ,搭建了一個安全的 resultful api ,接下來我們就進行簡單的測試,這里我是用postman,這是一個非常好用的 http 調(diào)試工具.
我們現(xiàn)在數(shù)據(jù)庫的users表中插入一條用戶信息,用戶名:itguang 密碼: 123456,
接下來,打開post滿,訪問 localhost/login?username=itguang&password=123456
如下:
我們可以看到響應頭中多了一個tokenproperties
token →Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJpdGd1YW5nIiwiZXhwIjoxNTE0OTU2NjI3fQ.PIiH7dRrVgPc88kOPtGzvrqZf5l87FRe3h7s9YZVb2zkL_XwRc_v3uhn23bmKqu7G0pSZngdnX0rh_kT1YDwww
這就是我們使用jwt生成的token,現(xiàn)在是加密狀態(tài),接下來我們再訪問 localhost/admin ,并把這個token放到 請求頭中,如下:
會看到返回了正確的字符串,但是如果我們不帶該token值呢?
瀏覽器訪問: http://localhost/admin ,會發(fā)現(xiàn)
403,明顯的沒有權(quán)限禁止訪問,這正是我們想要的結(jié)果.
關(guān)于“spring security和jwt整合的方法是什么”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對“spring security和jwt整合的方法是什么”知識都有一定的了解,大家如果還想學習更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
新聞名稱:springsecurity和jwt整合的方法是什么
分享URL:http://www.chinadenli.net/article30/pieoso.html
成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站內(nèi)鏈、面包屑導航、標簽優(yōu)化、響應式網(wǎng)站、網(wǎng)站策劃、App設計
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)