Spring Cloud之OAuth2

備:附件中OAuth2 授權服務器實現源碼及PPT
一、Authorization code grant


 
 
The flow illustrated in Figure 1 includes the following steps:
  • (A) The client (typically, a web application) initiates the flow by directing the
  • resource owner's user agent (typically, a web browser) to the authorization
  • endpoint. The client's request includes the client identifier, requested scope,
  • local state, and a redirection URI. The authorization server directs the user
  • agent (typically, a web browser) back to the redirect URI after the access is
  • granted (or denied).
  • (B) The resource owner authenticates with the authorization server through
  • the user agent and grants or denies the client's access request.
  • (C) If the resource owner grants access, the authorization server redirects the
  • user agent (typically, a web browser) back to the client using the redirection URI
  • provided earlier (in the request or during client registration). The redirection URI
  • includes an authorization code and any local state provided by the client earlier.
  • (D) The client makes an access token request from the authorization server's token
  • endpoint by including the authorization code received in the previous step. When
  • making the request, the client authenticates with the authorization server using
  • the client credentials. The client also includes the redirection URI used to obtain
  • the authorization code for verification.
  • (E) The authorization server authenticates the client. It validates the authorization
  • code and ensures that the redirection URI received matches the URI used to redirect
  • the client in step (C). If valid, the authorization server responds back with an access
  • token and, optionally, a refresh token in case an offline access was requested.
Authorization code request
The authorization code request corresponds to steps (A) and (B) as described in Figure
1. In step (A), the client makes a request to the authorization server in the 
application/x-www-form-urlencoded format, as shown in Listing 1.
Listing 1. Example of an authorization code request
1
2
3
GET /authorize?response_type=code&client_id=s6BhdRkqt3&state=xyz
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom%2Fcb HTTP/1.1
Host: server.example.com
The request must contain the following parameters:
  • response_type: Required. The value must be set to code.
  • client_id: Required. The client ID.
  • redirect_uri: Required. Used for user agent redirection.
  • scope: Optional. The scope of the access request.
  • state: Optional. To maintain the state between the request and callback.
After the request is verified by the authorization server, the server sends an HTTP
redirect code 302 response back to the client. The response will also include a redirection
URI in the http Location header. In step (B), the client must redirect the user agent
(typically, a web browser) to this URI. This redirection URI is usually a login page
where the resource owner can sign in with their credentials and grant/revoke access to the client's request.
Authorization code response
The authorization code response is shown in step (C) of Figure 1. If the resource
owner grants the access request, the authorization server issues an authorization
code. The authorization server redirects the user agent to the redirect URI provided
as a part of the request in step (A) and includes the authorization code as a part of
the query component of the redirection URI using the application/x-www-form-urlencodedformat.
The URI parameters are as follows:
  • Code: Required. The authorization code generated by the authorization server.
  • The code is temporary and must expire shortly after it was generated. The client
  • must not use the authorization code more than once. Any further requests using
  • the same code should be revoked by the authorization server. The authorization
  • code is bound to the client identifier and the redirection URI.
  • State: Required. If the state parameter was present in the client's authorization
  • code request, this parameter must be set to the exact value received from the client.
Access token request
This corresponds to step (D) in Figure 1. The client makes a request to the token
endpoint (authorization server) using the application/x-www-form-urlencoded format as shown in Listing 2.
Listing 2. Example of an access token request
1
2
3
4
5
6
7
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA
&redirect_uri=https%3A%2F%2Fclient%2Eexample%2Ecom&client_id=c342
The access token request must have the following parameters set:
  • grant_type: Required. The value must be set to authorization_code.
  • client_id: Required. The client ID.
  • client_secret: Optional. Secret. To authenticate with authorization server.
  • code: Required. The authorization code received from the server.
  • redirect_uri: Required. Identical to that sent in step (A).
The authorization server verifies that the code and redirect URI are valid. In the case
of confidential clients, the authorization server also authenticates the client using its
client credentials passed in the body of the request or in the authorization header.
Access token response
This corresponds to step (E) in Figure 1. If the access token request is valid and is
authorized, the authorization server returns the access token in an access token
response. An example of a successful response is shown in Listing 3.
Listing 3. Example of a successful access token response
1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"Bearer",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA",
"example_parameter":"example_value"
}
If the request is not valid or unauthorized, the authorization server returns an
appropriate error message with code.
Refresh access token request
This is an optional step, which is applicable if the client requested offline access
and was provided arefresh_token as a part of the access token request. An access token
is temporary and usually expires after an hour. After the access token expires, the
client would need to repeat the authentication process and the resource owner
would need to log in and provide authorization to enable the client to make the
access token request again.
If the client needs to refresh access tokens while the resource owner is not present
at the browser to log in and authenticate, the client uses the offline access. The
client can request an offline access while making the first authorization code
request (see step (A)). Under this scheme, the authorization server returns a refresh
token in addition to the access token. The refresh token is a long-living token that
does not expire, unless it is explicitly revoked by the resource owner. Every time
the access token expires, the client can use the refresh token to regenerate an
access token without the resource owner needing to sign in and authorize the access request.
The client makes a request to the token endpoint (authorization server) using
the application/x-www-form-urlencoded format, as shown in Listing 4:
Listing 4. Request to the token endpoint
1
2
3
4
5
6
POST /token HTTP/1.1
Host: server.example.com
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=tGzv3JOkF0XG5Qx2TlKWIA
The request parameters are defined as follows:
  • grant_type: Required. The value must be set to refresh_token.
  • refresh_token: Required. This is retrieved earlier from access token request.
  • scope: Optional. The scope of the access request.
The authorization server verifies the refresh token and issues a new access token.
Refresh access token response
If the request is successful, the authorization server returns a new access token.
An example of a successful response is shown in Listing 5.
Listing 5. Refresh access token response
1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
"access_token":"2YotnFZFEjr1zCsicMWpAA",
"token_type":"Bearer",
"expires_in":3600,
"example_parameter":"example_value"
}
If the request is not valid or unauthorized, the authorization server returns an
appropriate error message with code.
 
 
二、用戶評證
 
 
 
三、SAML/JWT


JWT:
 


 

 
 
 
 
 
SAML:


 
 
SAML  2 種典型模式
在協議的角度, SAML 原理非常類似 CAS  Kerberos  CAS 協議依賴於 CAS Server  
Kerberos依賴於 KDC ,而 SAML 則依賴於 Identity Provider 
根據 Service Provider( 以下簡稱 SP)  Identity Provider( 以下簡稱 IDP) 的交互方式, 
SAML 可以分爲以下幾種模式:一種是 SP 拉方式,一種是 IDP 推方式。
 SAML 中,最重要的環節是 SP 如何獲取對 Subject 的斷言, SP 拉方式是 SP 主動到 IDP 
去了解Subject 的身份斷言,而 IDP 推方式則是 IDP 主動把 Subject 的身份斷言通過某種途徑告訴 SP 
2.2.1 SAML  POST/Artifact Bindings 方式(即 SP 拉方式)
該方式的主要特點是, SP 獲得客戶端的憑證  IDP  Subject 的一種身份認可 之後,主動請求 
IDP Subject 的憑證的斷言。如下圖所示: Subject 是根據憑證去訪問 SP 的。憑證代表了 
Subject 的身份,它類似於“來自 IDP 證明:我就是 Peter ,法國公民”。
現在,讓我們看看 SP 拉方式是如何進行的:
Subject 訪問 SP 的受保護資源, SP 發現 Subject 的請求中沒有包含任何的授權信息,
於是它重定向用戶訪問 IDP.
 

      
 
協議執行:
1, Subject  IDP 請求憑證 方式是提交用戶名 / 密碼
2, IDP 通過驗證 Subject 提供的信息,來確定是否提供憑證給 Subject
3, 假如 Subject 的驗證信息正確,他將獲取 IDP 的憑證以及將服務請求同時提交給 SP 
4, SP 接受到 Subject 的憑證,它是提供服務之前必須驗證次憑證,於是,它產生了一個 
SAML 請求,要求 IDP 對憑證斷言
5, 憑證是 IDP 產生的,它當然知道憑證的內容,於是它迴應一個 SAML 斷言給 SP
6, SP 信任 IDP  SAML 斷言,它會根據斷言結果確定是否爲 Subject 提供服務。
4.2.1 SAML  Redirect/POST Bindings 方式  IDP 推方式
該方式的主要特點是, IDP 交給 Subject 的不是憑證,而是斷言。
過程如下圖所示:
 

 
       1  Subject 訪問 SP 的授權服務, SP 重定向 Subject  IDP 獲取斷言。
       2  IDP 會要求 Subject 提供能夠證明它自己身份的手段 (Password  X.509 證書等
       3  Subject  IDP 提供了自己的帳號密碼。
       4  IDP 驗證密碼之後,會重訂向 Subject 到原來的 SP 
       5  SP 校驗 IDP 的斷言 注意, IDP 會對自己的斷言簽名, SP 信任 IDP 的證書,因此,
通過校驗簽名,能夠確信從 Subject 過來的斷言確實來自 IDP 的斷言 
       6 ,如果簽名正確, SP 將向 Subject 提供該服務。
 
 
四、Spring + Auth2 + Security


 
五、Authorization Server Configuration
 
 
 
 
問題:
{"timestamp":1420442772928,"status":401,"error":"Unauthorized",
"message":"Full authentication is required to access this resource","path":"/resource"}
解決辦法:
Authorization:username:password
用戶名密碼需要通過Request Header傳遞(key=Authorization,value=base64(username:password))
 
Application.yml:
server.contextPath: /auth
logging:
level:
org.springframework.security: DEBUG
server:
port: 8080
keystore:
password: mySecretKey
security:
user:
name:admin
password:admin
oauth2:
client:
clientId:acme
clientSecret:acmesecret
authorized-grant-types:authorization_code,refresh_token,password
scope:openid
 
 
六、TOKEN 存儲方式
(一)InMemoryTokenStore
TOKEN存儲方式爲默認的配置方式,AuthorizationServerEndpointsConfigurer類如下
private TokenStore tokenStore() {
if (tokenStore == null) {
if (accessTokenConverter() instanceof JwtAccessTokenConverter) {
this.tokenStore = new JwtTokenStore((JwtAccessTokenConverter) accessTokenConverter());
}
else {
this.tokenStore = new InMemoryTokenStore();
}
}
return this.tokenStore;
}
 
TOKEN生成策略接口(AuthenticationKeyGenerator),從OAuth2Authentication對
象生成唯一TOKEN KEY,基於內存TOKEN,默認生成KEY實現類爲:
DefaultAuthenticationKeyGenerator,MD5算法簽名

public class DefaultAuthenticationKeyGenerator implements AuthenticationKeyGenerator {
private static final String CLIENT_ID = "client_id";
private static final String SCOPE = "scope";
private static final String USERNAME = "username";
public String extractKey(OAuth2Authentication authentication) {
Map<String, String> values = new LinkedHashMap<String, String>();
OAuth2Request authorizationRequest = authentication.getOAuth2Request();
if (!authentication.isClientOnly()) {
values.put(USERNAME, authentication.getName());
}
values.put(CLIENT_ID, authorizationRequest.getClientId());
if (authorizationRequest.getScope() != null) {
values.put(SCOPE, OAuth2Utils.formatParameterList(new TreeSet<String>
(authorizationRequest.getScope())));
}
return generateKey(values);
}
protected String generateKey(Map<String, String> values) {
MessageDigest digest;
try {
digest = MessageDigest.getInstance("MD5");
byte[] bytes = digest.digest(values.toString().getBytes("UTF-8"));
return String.format("%032x", new BigInteger(1, bytes));
} catch (NoSuchAlgorithmException nsae) {
throw new IllegalStateException("MD5 algorithm not available. Fatal (should be in the JDK).", nsae);
} catch (UnsupportedEncodingException uee) {
throw new IllegalStateException("UTF-8 encoding not available. Fatal (should be in the JDK).", uee);
}
}
}
 
可以調用以下方法重寫TOKEN生成方式,限於高級用法
public void setAuthenticationKeyGenerator(AuthenticationKeyGenerator authenticationKeyGenerator) {
this.authenticationKeyGenerator = authenticationKeyGenerator;
}
 

 
 
(二)JwtTokenStore
JwtTokenStore實現需要依賴AccessTokenConverter接口,AccessTokenConverter
接口實現類有兩個:
1)JWT(header、content、cryto)----JwtAccessTokenConverter接口
2)普通 ------DefaultAccessTokenConverter接口(默認實現)
參考類AuthorizationServerEndpointsConfigurer:

private AccessTokenConverter accessTokenConverter() {
if (this.accessTokenConverter == null) {
accessTokenConverter = new DefaultAccessTokenConverter();
}
return this.accessTokenConverter;
}
private TokenStore tokenStore() {
if (tokenStore == null) {
if (accessTokenConverter() instanceof JwtAccessTokenConverter) {
this.tokenStore = new JwtTokenStore((JwtAccessTokenConverter)
accessTokenConverter());
}
else {
this.tokenStore = new InMemoryTokenStore();
}
}
return this.tokenStore;
}

JWT轉換方式:
 
1)MAC (默認)
採用對用戶(字段username)或者客戶端信息(oauthrocations 中的AUTHORITIES)進簽名,需要依賴
DefaultAccessTokenConverter實現,DefaultAccessTokenConverter又需要
UserAuthenticationConverter接口實現(只有一個實現類DefaultUserAuthenticationConverter),
DefaultUserAuthenticationConverter實現需要依賴UserDetailsService接口獲取用戶信息
簽名屬性:

public class DefaultUserAuthenticationConverter implements UserAuthenticationConverter {
private Collection<? extends GrantedAuthority> defaultAuthorities;
private UserDetailsService userDetailsService;
/**
* Optional {@link UserDetailsService} to use when extracting an {@link Authentication}
from the incoming map.
*
* @param userDetailsService the userDetailsService to set
*/
public void setUserDetailsService(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
/**
* Default value for authorities if an Authentication is being created and the input has
no data for authorities.
* Note that unless this property is set, the default Authentication created by
{@link #extractAuthentication(Map)}
* will be unauthenticated.
*
* @param defaultAuthorities the defaultAuthorities to set. Default null.
*/
public void setDefaultAuthorities(String[] defaultAuthorities) {
this.defaultAuthorities = AuthorityUtils.commaSeparatedStringToAuthorityList(StringUtils
.arrayToCommaDelimitedString(defaultAuthorities));
}
public Map<String, ?> convertUserAuthentication(Authentication authentication) {
Map<String, Object> response = new LinkedHashMap<String, Object>();
response.put(USERNAME, authentication.getName());
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
return response;
}
public Map<String, ?> convertUserAuthentication(Authentication authentication) {
Map<String, Object> response = new LinkedHashMap<String, Object>();
response.put(USERNAME, authentication.getName());
if (authentication.getAuthorities() != null && !authentication.getAuthorities().isEmpty()) {
response.put(AUTHORITIES, AuthorityUtils.authorityListToSet(authentication.getAuthorities()));
}
return response;
}

簽名方法:
// Key隨機生成
private String verifierKey = new RandomValueStringGenerator().generate();
private Signer signer = new MacSigner(verifierKey);
private String signingKey = verifierKey;
 

 
/**
* Get the verification key for the token signatures.
*
* @return the key used to verify tokens
*/
public Map<String, String> getKey() {
Map<String, String> result = new LinkedHashMap<String, String>();
result.put("alg", signer.algorithm());
result.put("value", verifierKey);
return result;
}
 
public class MacSigner implements SignerVerifier {
private static final String DEFAULT_ALGORITHM = "HMACSHA256";
public byte[] sign(byte[] bytes) {
try {
Mac mac = Mac.getInstance(algorithm);
mac.init(key);
return mac.doFinal(bytes);
}
catch (GeneralSecurityException e) {
throw new RuntimeException(e);
}
}

 
2)RSA
 

public void setKeyPair(KeyPair keyPair) {
PrivateKey privateKey = keyPair.getPrivate();
Assert.state(privateKey instanceof RSAPrivateKey, "KeyPair must be an RSA ");
signer = new RsaSigner((RSAPrivateKey) privateKey);
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
verifier = new RsaVerifier(publicKey);
verifierKey = "-----BEGIN PUBLIC KEY-----\n" + new String(Base64.encode(publicKey.getEncoded()))
+ "\n-----END PUBLIC KEY-----";
}

 
 
(三)JdbcTokenStore
 
 
 
(四)RedisTokenStore
 
 
AbstractEndpoint
 
1)AuthorizationEndpoint---/oauth/authorize
 
2)TokenEndpoint---/oauth/token
 
3)CheckTokenEndpoint---/oauth/check_token
 
4)WhitelabelApprovalEndpoint---/oauth/confirm_acces
 
5)TokenKeyEndpoint---/oauth/token_key
 
 
七、Spring Security
 
一、基礎配置
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired MyUserDetailsService detailsService;
@Override protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() .and().formLogin().loginPage("/login").permitAll()
.defaultSuccessUrl("/", true) .and().logout().logoutUrl("/logout")
.and().sessionManagement().maximumSessions(1).expiredUrl("/expired")
.and() .and().exceptionHandling().accessDeniedPage("/accessDenied");
}
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers("/js/**", "/css/**", "/images/**", "/**/favicon.ico");
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(detailsService).passwordEncoder(new BCryptPasswordEncoder());
}
}
1.@EnableWebSecurity: 禁用Boot的默認Security配置,配合@Configuration啓用自定義配置
(需要擴展WebSecurityConfigurerAdapter)
2.@EnableGlobalMethodSecurity(prePostEnabled = true): 啓用Security註解
例如最常用的@PreAuthorize
3.configure(HttpSecurity): Request層面的配置,對應XML Configuration中的<http>元素
4.configure(WebSecurity): Web層面的配置,一般用來配置無需安全檢查的路徑
5.configure(AuthenticationManagerBuilder): 身份驗證配置,用於注入
自定義身份驗證Bean密碼校驗規則
 
二、擴展配置
 
完成基礎配置之後,下一步就是實現自己的UserDetailsService和PermissionEvaluator,
分別用於自定義Principle, Authority和Permission。
@Component
public class MyUserDetailsService implements UserDetailsService {
@Autowired private LoginService loginService;
@Autowired private RoleService roleService;
@Override
public UserDetails loadUserByUsername(String username) {
if (StringUtils.isBlank(username)) {
throw new UsernameNotFoundException("用戶名爲空");
}
Login login = loginService.findByUsername(username).orElseThrow(() -> new UsernameNotFoundException("用戶不存在"));
Set<GrantedAuthority> authorities = new HashSet<>();
roleService.getRoles(login.getId()).forEach(r -> authorities.add(new SimpleGrantedAuthority(r.getName())));
return new org.springframework.security.core.userdetails.User( username, login.getPassword(),
true,//是否可用 true,//是否過期 true,//證書不過期爲true true,//賬戶未鎖定爲true authorities);
}
}
創建GrantedAuthority對象時,一般名稱加上ROLE_前綴。
@Component
public class MyPermissionEvaluator implements PermissionEvaluator {
@Autowired private LoginService loginService;
@Autowired private RoleService roleService;
@Override
public boolean hasPermission(Authentication authentication, Object targetDomainObject, Object permission) {
String username = authentication.getName();
Login login = loginService.findByUsername(username).get();
return roleService.authorized(login.getId(), targetDomainObject.toString(), permission.toString());
}
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId,
String targetType, Object permission) {
// not supported return false;
}
}
1. hasPermission(Authentication, Object, Object)和hasPermission(Authentication,
Serializable, String, Object)
兩個方法分別對應Spring Security中兩個同名的表達式。
 
 
八、授權頁修改
 
1) 修改管理端點URL
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
MyAuthorizationCodeService authorizationCodeServices = authorizationCodeServices();
authorizationCodeServices.setClientDetailsService(endpoints.getClientDetailsService());
endpoints.tokenStore(tokenStore())
.tokenEnhancer(jwtTokenEnhancer())
.authorizationCodeServices(authorizationCodeServices)
.authenticationManager(authenticationManager)
.pathMapping("/oauth/authorize", "/oauth2/authorize")
.pathMapping("/oauth/token", "/oauth2/token")
.pathMapping("/oauth/check_token", "/oauth2/check_token")
.pathMapping("/oauth/confirm_access", "/oauth2/confirm_access")
.pathMapping("/oauth/token_key", "/oauth2/token_key")
.pathMapping("/oauth/error", "/oauth2/error");
}
 
2)但是授權確認頁面表單寫死Action
端點類:WhitelabelApprovalEndpoint
@FrameworkEndpoint
@SessionAttributes("authorizationRequest")
public class WhitelabelApprovalEndpoint {
 
@RequestMapping("/oauth/confirm_access")
public ModelAndView getAccessConfirmation(Map<String, Object> model,
HttpServletRequest request) throws Exception {
String template = createTemplate(model, request);
if (request.getAttribute("_csrf") != null) {
model.put("_csrf", request.getAttribute("_csrf"));
}
return new ModelAndView(new SpelView(template), model);
}
 
protected String createTemplate(Map<String, Object> model, HttpServletRequest request) {
String template = TEMPLATE;
if (model.containsKey("scopes") || request.getAttribute("scopes") != null) {
template = template.replace("%scopes%", createScopes(model, request)).replace("%denial%", "");
}
else {
template = template.replace("%scopes%", "").replace("%denial%", DENIAL);
}
if (model.containsKey("_csrf") || request.getAttribute("_csrf") != null) {
template = template.replace("%csrf%", CSRF);
}
else {
template = template.replace("%csrf%", "");
}
return template;
}
 
private CharSequence createScopes(Map<String, Object> model, HttpServletRequest request) {
StringBuilder builder = new StringBuilder("<ul>");
@SuppressWarnings("unchecked")
Map<String, String> scopes = (Map<String, String>) (model.containsKey("scopes") ? model.get("scopes") : request
.getAttribute("scopes"));
for (String scope : scopes.keySet()) {
String approved = "true".equals(scopes.get(scope)) ? " checked" : "";
String denied = !"true".equals(scopes.get(scope)) ? " checked" : "";
String value = SCOPE.replace("%scope%", scope).replace("%key%", scope).replace("%approved%", approved)
.replace("%denied%", denied);
builder.append(value);
}
builder.append("</ul>");
return builder.toString();
}
 
private static String CSRF = "<input type='hidden' name='${_csrf.parameterName}' value='${_csrf.token}' />";
 
private static String DENIAL = "<form id='denialForm' name='denialForm' action='${path}
/oauth/authorize' method='post'><input name='user_oauth_approval' value='false'
type='hidden'/>%csrf%<label><input name='deny' value='Deny' type='submit'/></label></form>";
 
private static String TEMPLATE = "<html><body><h1>OAuth Approval</h1>"
+ "<p>Do you authorize '${authorizationRequest.clientId}' to access your protected
resources?</p>"
+ "<form id='confirmationForm' name='confirmationForm' action='${path}
/oauth/authorize' method='post'><input name='user_oauth_approval'
value='true' type='hidden'/>%csrf%%scopes%<label><input name='authorize'
value='Authorize' type='submit'/></label></form>"
+ "%denial%</body></html>";
 
private static String SCOPE = "<li><div class='form-group'>%scope%:
<input type='radio' name='%key%'"
+ " value='true'%approved%>Approve</input> <input type='radio'
name='%key%' value='false'%denied%>Deny</input></div></li>";
}
3)解決辦法
配置自動授權,則跳過第2步,直接獲取授權碼
builder
.withClient("SheChe")
.secret("12345678")
.authorizedGrantTypes("client_credentials","authorization_code")
.scopes("resource-server-read", "resource-server-write")
.accessTokenValiditySeconds(3600)
.autoApprove(true);
 
 
 
九、www-authenticate認證  
 
很多驗證都採用這種驗證方式,尤其在嵌入式領域中。
優點:方便
缺點:這種認證方式在傳輸過程中採用的用戶名密碼加密方式爲
BASE-64,其解碼過程非常簡單,如果被嗅探密碼幾乎是透明的.
 
服務器收到請求後,首先會解析發送來的數據中是否包含有:
Authorization: Basic XXXX=這種格式的數據
如果沒有這樣的header數據
那麼服務器會發送HTTP信息頭WWW-Authenticate: Basic realm=""到瀏覽器
要求瀏覽器發送合法的用戶名和密碼到服務端,爲了進一步告知瀏覽器,
這個頁面需要認證 我們最還還是接着發送一個401錯誤
Header("HTTP/1.0 401 Unauthorized");
 
用戶輸入用戶名:admin 密碼:admin後,瀏覽器將以下面這種格式將
數據發送給服務器端:Authorization: Basic YWRtaW46YWRtaW4=
Authorization: Basic爲www-authenticate認證的標準HTTP信息頭
YWRtaW46YWRtaW4=是經BASE-64加密後的用戶名和密碼
 
 
ajax跨域訪問是一個老問題了,解決方法很多,比較常用的是JSONP方法,
JSONP方法是一種非官方方法,而且這種方法只支持GET方式,不如POST方式安全。
即使使用jQuery的jsonp方法,type設爲POST,也會自動變爲GET。
 
官方問題說明:
“script”: Evaluates the response as JavaScript and returns it as plain text.
Disables caching by appending a query string parameter, “_=[TIMESTAMP]“,
to the URL unless the cache option is set to true.Note: This will turn POSTs into GETs for remote-domain requests.
 
如果跨域使用POST方式,可以使用創建一個隱藏的iframe來實現,與ajax上傳圖片原理一樣,但這樣會比較麻煩。
 
因此,通過設置Access-Control-Allow-Origin來實現跨域訪問比較簡單。
例如:客戶端的域名是www.client.com,而請求的域名是www.server.com
如果直接使用ajax訪問,會有以下錯誤
XMLHttpRequest cannot load http://www.server.com/server.PHP. No 'Access-Control-Allow-Origin'
header is present on the requested resource.Origin 'http://www.client.com' is therefore not allowed access.
在被請求的Response header中加入
// 指定允許其他域名訪問  
  1. header('Access-Control-Allow-Origin:*');  
  2. // 響應類型  
  3. header('Access-Control-Allow-Methods:POST');  
  4. // 響應頭設置  
  5. header('Access-Control-Allow-Headers:x-requested-with,content-type');  
就可以實現ajax POST跨域訪問了
 
Access-Control-Allow-Origin:* 表示允許任何域名跨域訪問
如果需要指定某域名才允許跨域訪問,只需把Access-Control-Allow-Origin:*
改爲Access-Control-Allow-Origin:允許的域名
例如:header('Access-Control-Allow-Origin:http://www.client.com');
 
如果需要設置多個域名允許訪問,這裏需要用php處理一下
例如允許 www.client.com 與 www.client2.com 可以跨域訪問
 
十一、Spring Security
 
 
authentication (who are you?) and authorization (what are you allowed to do?)
Authentication
The main strategy interface for authentication is AuthenticationManager which only has one method:
public interface AuthenticationManager { Authentication authenticate(Authentication
authentication) throws AuthenticationException; }
An AuthenticationManager can do one of 3 things in its authenticate() method:
1. return an Authentication (normally with authenticated=true) if it can verify that the
input represents a valid principal.
2. throw an AuthenticationException if it believes that the input represents an invalid principal.
3. return null if it can’t decide.
 
 
The most commonly used implementation of AuthenticationManager is 
ProviderManager, which delegates to a chain of AuthenticationProvider instances.
AnAuthenticationProvider is a bit like an AuthenticationManager but it has an
extra method to allow the caller to query if it supports a given Authentication type:
publicinterfaceAuthenticationProvider{Authentication authenticate
(Authentication authentication)throwsAuthenticationException;boolean
supports(Class<?> authentication);}
 
Sometimes an application has logical groups of protected resources (e.g. all web resources
that match a path pattern /api/**), and each group can have its own dedicated
AuthenticationManager. Often, each of those is a ProviderManager, and they
share a parent. The parent is then a kind of "global" resource, acting as a fallback for all providers.


 
 
Spring 訪問決策管理
 
AccessDecisionManager
1. AffirmativeBased 一票可通過
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int deny = 0;
 
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
 
if (logger.isDebugEnabled()) {
logger.debug("Voter: " + voter + ", returned: " + result);
}
 
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
return;
 
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
 
break;
 
default:
break;
}
}
 
if (deny > 0) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
 
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
 
2. ConsensusBased 贊成>大於反對
 
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {
int grant = 0;
int deny = 0;
int abstain = 0;
 
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, configAttributes);
 
if (logger.isDebugEnabled()) {
logger.debug("Voter: " + voter + ", returned: " + result);
}
 
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
grant++;
 
break;
 
case AccessDecisionVoter.ACCESS_DENIED:
deny++;
 
break;
 
default:
abstain++;
 
break;
}
}
 
if (grant > deny) {
return;
}
 
if (deny > grant) {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
 
if ((grant == deny) && (grant != 0)) {
if (this.allowIfEqualGrantedDeniedDecisions) {
return;
}
else {
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied", "Access is denied"));
}
}
 
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
 
 
3. UnanimousBased 全票通過
public void decide(Authentication authentication, Object object,
Collection<ConfigAttribute> attributes) throws AccessDeniedException {
 
int grant = 0;
int abstain = 0;
 
List<ConfigAttribute> singleAttributeList = new ArrayList<ConfigAttribute>(1);
singleAttributeList.add(null);
 
for (ConfigAttribute attribute : attributes) {
singleAttributeList.set(0, attribute);
 
for (AccessDecisionVoter voter : getDecisionVoters()) {
int result = voter.vote(authentication, object, singleAttributeList);
 
if (logger.isDebugEnabled()) {
logger.debug("Voter: " + voter + ", returned: " + result);
}
switch (result) {
case AccessDecisionVoter.ACCESS_GRANTED:
grant++;
 
break;
 
case AccessDecisionVoter.ACCESS_DENIED:
throw new AccessDeniedException(messages.getMessage(
"AbstractAccessDecisionManager.accessDenied",
"Access is denied"));
 
default:
abstain++;
 
break;
}
}
}
// To get this far, there were no deny votes
if (grant > 0) {
return;
}
// To get this far, every AccessDecisionVoter abstained
checkAllowIfAllAbstainDecisions();
}
 
AccessDecisionVoter
1. RoleVoter
2. AuthenticatedVoter
 
 
 
十二、Spring授權碼換TOKEN
 
Redirect_url 驗證一致性
 
 
 
參考資料:
// OAuth2 in depth: A step-by-step introduction for enterprises
 
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章