java程式設計spring boot security oauth2 jwt完美整合例子
阿新 • • 發佈:2019-02-05
一、本文簡介
本文主要講解Java程式設計中spring boot框架+spring security框架+spring security oauth2框架整合的例子,並且oauth2整合使用jwt方式儲存二、學習前提
首先是講解oauth2的基本說明:推薦檢視:http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html
上面的地址可以基本瞭解下oauth2的原理
JWT的認知和基本使用:
推薦檢視:
1.什麼是JWT?
2.Java程式設計中java-jwt框架使用
三、程式碼編輯
開始程式碼:認證服務:
幾個核心的配置類:
AuthorizationServerConfiguration.java
package com.leftso.config.security;
import java.util.HashMap;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
/**
* 認證授權服務端
*
* @author leftso
*
*/
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
@Value("${resource.id:spring-boot-application}") // 預設值spring-boot-application
private String resourceId;
@Value("${access_token.validity_period:3600}") // 預設值3600
int accessTokenValiditySeconds = 3600;
@Autowired
private AuthenticationManager authenticationManager;
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
endpoints.authenticationManager(this.authenticationManager);
endpoints.accessTokenConverter(accessTokenConverter());
endpoints.tokenStore(tokenStore());
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.tokenKeyAccess("isAnonymous() || hasAuthority('ROLE_TRUSTED_CLIENT')");
oauthServer.checkTokenAccess("hasAuthority('ROLE_TRUSTED_CLIENT')");
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
clients.inMemory().withClient("normal-app").authorizedGrantTypes("authorization_code", "implicit")
.authorities("ROLE_CLIENT").scopes("read", "write").resourceIds(resourceId)
.accessTokenValiditySeconds(accessTokenValiditySeconds).and().withClient("trusted-app")
.authorizedGrantTypes("client_credentials", "password").authorities("ROLE_TRUSTED_CLIENT")
.scopes("read", "write").resourceIds(resourceId).accessTokenValiditySeconds(accessTokenValiditySeconds)
.secret("secret");
}
/**
* token converter
*
* @return
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {
/***
* 重寫增強token方法,用於自定義一些token返回的資訊
*/
@Override
public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
String userName = authentication.getUserAuthentication().getName();
User user = (User) authentication.getUserAuthentication().getPrincipal();// 與登入時候放進去的UserDetail實現類一直檢視link{SecurityConfiguration}
/** 自定義一些token屬性 ***/
final Map<String, Object> additionalInformation = new HashMap<>();
additionalInformation.put("userName", userName);
additionalInformation.put("roles", user.getAuthorities());
((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication);
return enhancedToken;
}
};
accessTokenConverter.setSigningKey("123");// 測試用,資源服務使用相同的字元達到一個對稱加密的效果,生產時候使用RSA非對稱加密方式
return accessTokenConverter;
}
/**
* token store
*
* @param accessTokenConverter
* @return
*/
@Bean
public TokenStore tokenStore() {
TokenStore tokenStore = new JwtTokenStore(accessTokenConverter());
return tokenStore;
}
}
SecurityConfiguration.java
package com.leftso.config.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import com.leftso.entity.Account;
import com.leftso.repository.AccountRepository;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
// 查詢使用者使用
@Autowired
AccountRepository accountRepository;
@Autowired
public void globalUserDetails(AuthenticationManagerBuilder auth) throws Exception {
// auth.inMemoryAuthentication()
// .withUser("user").password("password").roles("USER")
// .and()
// .withUser("app_client").password("nopass").roles("USER")
// .and()
// .withUser("admin").password("password").roles("ADMIN");
//配置使用者來源於資料庫
auth.userDetailsService(userDetailsService());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll().anyRequest().authenticated().and()
.httpBasic().and().csrf().disable();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public UserDetailsService userDetailsService() {
return new UserDetailsService() {
@Override
public UserDetails loadUserByUsername(String name) throws UsernameNotFoundException {
// 通過使用者名稱獲取使用者資訊
Account account = accountRepository.findByName(name);
if (account != null) {
// 建立spring security安全使用者
User user = new User(account.getName(), account.getPassword(),
AuthorityUtils.createAuthorityList(account.getRoles()));
return user;
} else {
throw new UsernameNotFoundException("使用者[" + name + "]不存在");
}
}
};
}
}
受保護的資源服務:
核心配置:
SecurityConfiguration.java
package com.leftso.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated()
.and().httpBasic()
.and().csrf().disable();
}
}
ResourceServerConfiguration.java
package com.leftso.config;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;
import org.springframework.security.web.util.matcher.RequestMatcher;
/**
* 資源服務端
*
* @author leftso
*
*/
@Configuration
@EnableResourceServer
public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Value("${resource.id:spring-boot-application}")
private String resourceId;
@Override
public void configure(ResourceServerSecurityConfigurer resources) {
// @formatter:off
resources.resourceId(resourceId);
resources.tokenServices(defaultTokenServices());
// @formatter:on
}
@Override
public void configure(HttpSecurity http) throws Exception {
// @formatter:off
http.requestMatcher(new OAuthRequestedMatcher()).authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated();
// @formatter:on
}
private static class OAuthRequestedMatcher implements RequestMatcher {
public boolean matches(HttpServletRequest request) {
String auth = request.getHeader("Authorization");
// Determine if the client request contained an OAuth Authorization
boolean haveOauth2Token = (auth != null) && auth.startsWith("Bearer");
boolean haveAccessToken = request.getParameter("access_token") != null;
return haveOauth2Token || haveAccessToken;
}
}
// ===================================================以下程式碼與認證伺服器一致=========================================
/**
* token儲存,這裡使用jwt方式儲存
*
* @param accessTokenConverter
* @return
*/
@Bean
public TokenStore tokenStore() {
TokenStore tokenStore = new JwtTokenStore(accessTokenConverter());
return tokenStore;
}
/**
* Token轉換器必須與認證服務一致
*
* @return
*/
@Bean
public JwtAccessTokenConverter accessTokenConverter() {
JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter() {
// /***
// * 重寫增強token方法,用於自定義一些token返回的資訊
// */
// @Override
// public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
// String userName = authentication.getUserAuthentication().getName();
// User user = (User) authentication.getUserAuthentication().getPrincipal();// 與登入時候放進去的UserDetail實現類一直檢視link{SecurityConfiguration}
// /** 自定義一些token屬性 ***/
// final Map<String, Object> additionalInformation = new HashMap<>();
// additionalInformation.put("userName", userName);
// additionalInformation.put("roles", user.getAuthorities());
// ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInformation);
// OAuth2AccessToken enhancedToken = super.enhance(accessToken, authentication);
// return enhancedToken;
// }
};
accessTokenConverter.setSigningKey("123");// 測試用,授權服務使用相同的字元達到一個對稱加密的效果,生產時候使用RSA非對稱加密方式
return accessTokenConverter;
}
/**
* 建立一個預設的資源服務token
*
* @return
*/
@Bean
public ResourceServerTokenServices defaultTokenServices() {
final DefaultTokenServices defaultTokenServices = new DefaultTokenServices();
defaultTokenServices.setTokenEnhancer(accessTokenConverter());
defaultTokenServices.setTokenStore(tokenStore());
return defaultTokenServices;
}
// ===================================================以上程式碼與認證伺服器一致=========================================
}
專案原始碼下載:
https://github.com/leftso/demo-spring-boot-security-oauth2
四、測試上面的編碼
測試過程步驟一:開啟瀏覽器,輸入地址
http://localhost:8080/oauth/authorize?client_id=normal-app&response_type=code&scope=read&redirect_uri=/resources/user
會提示輸入使用者名稱密碼,這時候輸入使用者名稱leftso,密碼111aaa將會出現以下介面
點選Authorize將獲取一個隨機的code,如圖:
開啟工具postmain,輸入以下地址獲取授權token
localhost:8080/oauth/token?code=r8YBUL&grant_type=authorization_code&client_id=normal-app&redirect_uri=/resources/user
注意:url中的code就是剛才瀏覽器獲取的code值獲取的token資訊如下圖:
這時候拿到token就可以訪問受保護的資源資訊了,如下
localhost:8081//resources/user
首先,直接訪問資源,會報錯401如圖:
我們加上前面獲取的access token再試:
localhost:8081//resources/user?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOlsic3ByaW5nLWJvb3QtYXBwbGljYXRpb24iXSwidXNlcl9uYW1lIjoibGVmdHNvIiwic2NvcGUiOlsicmVhZCJdLCJyb2xlcyI6W3siYXV0aG9yaXR5IjoiUk9MRV9VU0VSIn1dLCJleHAiOjE0OTEzNTkyMjksInVzZXJOYW1lIjoibGVmdHNvIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9VU0VSIl0sImp0aSI6IjgxNjI5NzQwLTRhZWQtNDM1Yy05MmM3LWZhOWIyODk5NmYzMiIsImNsaWVudF9pZCI6Im5vcm1hbC1hcHAifQ.YhDJkMSlyIN6uPfSFPbfRuufndvylRmuGkrdprUSJIM
這時候我們就能成功獲取受保護的資源資訊了:
到這裡spring boot整合security oauth2 的基本使用已經講解完畢.
五、擴充套件思維
留下一些擴充套件1.認證服務的客戶端資訊是存放記憶體的,實際應用肯定是不會放記憶體的,考慮資料庫,預設有個DataSource的方式,還有一個自己實現clientDetail介面方式
2.jwt這裡測試用的最簡單的對稱加密,實際應用中使用的一般都是RSA非對稱加密方式