备:附件中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方式安全。
官方问题说明:
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访问,会有以下错误
header is present on the requested resource.Origin 'http://www.client.com' is therefore not allowed access.
在被请求的Response header中加入
// 指定允许其他域名访问
- header('Access-Control-Allow-Origin:*');
- // 响应类型
- header('Access-Control-Allow-Methods:POST');
- // 响应头设置
- 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
相关推荐
在Spring Cloud中,我们可以使用Spring Security OAuth2模块来实现OAuth2的授权流程。 集成OAuth2的过程通常包括以下几个步骤: 1. **配置授权服务器**:授权服务器是负责处理用户登录、授权和令牌发放的组件。在...
在Spring Cloud中,我们可以使用Spring Security OAuth2模块来实现OAuth2的授权服务器和资源服务器。 1. **OAuth2流程**: - 授权请求:客户端(如浏览器或移动应用)引导用户到授权服务器进行登录。 - 用户授权...
基于 Spring Cloud Hoxton 、Spring Boot 2.2、 OAuth2 的RBAC权限管理系统 基于数据驱动视图的理念封装 Ant Design Vue,即使没有 vue 的使用经验也能快速上手 提供 lambda 、stream api 、webflux 的生产实践 ...
`SpringCloud` 和 `OAuth2` 的结合提供了一种高效且灵活的方式来实现微服务架构中的服务发现、负载均衡和安全控制。本文将深入探讨如何利用这两个强大的框架来构建一个实现单点登录(Single Sign-On,SSO)及安全...
系统说明:基于 Spring Cloud 2021 、Spring Boot 2.6、 OAuth2 的 RBAC 权限管理系统 基于数据驱动视图的理念封装 element-ui,即使没有 vue 的使用经验也能快速上手 提供对常见容器化支持 Docker、Kubernetes、...
基于Spring Cloud Finchley.SR1 网关基于 Spring Cloud Gateway 提供Consul 服务注册发现版本pigxc 最终一致性的分布式事务解决方案 图形化代码生成,不会vue也能做到敏捷开发 基于 Spring Security oAuth 深度定制...
springCloud-分享版: 基于Spring Cloud、OAuth2.0的前后端分离的系统。 通用RBAC权限设计及其数据权限和分库分表 支持服务限流、动态路由、灰度发布、 支持常见登录方式, 多系统SSO登录 基于 Spring Cloud Hoxton ...
这是一个基于最新技术栈,包括Spring Cloud 2021、Spring Boot 2.7和OAuth2的RBAC(Role-Based Access Control)权限管理系统的源码项目。该项目旨在提供一套高效、安全的后端服务框架,用于实现用户权限的精细化...
SpringCloud+SpringBoot+OAuth2+Spring Security+Redis实现的微服务统一认证授权
SpringCloud(八):API网关整合OAuth2认证授权服务。
Spring Cloud OAuth2结合Zuul,为微服务架构提供了一种安全、高效的身份验证和授权解决方案。以下是对这些技术的详细解释: **OAuth2** 是一个开放标准,主要用于授权。它不直接处理用户身份验证,但允许第三方应用...
Spring Cloud OAuth2是一种基于OAuth2协议的授权框架,它为微服务架构提供了安全的认证和授权服务。在“Spring Cloud Oauth2的密码模式数据库方式实现登录授权验证”这个主题中,我们将深入探讨如何利用OAuth2的密码...
在构建基于Spring Cloud、OAuth2.0和Vue的前后端分离系统时,我们涉及了多个核心技术和实践,这些技术在现代分布式系统中扮演着至关重要的角色。以下是对这些知识点的详细说明: 1. **Spring Cloud**: Spring ...
Spring Cloud OAuth2 是一个基于 Spring Boot 和 Spring Security 的 OAuth2 实现,用于为微服务架构提供安全认证和授权服务。OAuth2 是一个开放标准,它允许用户授权第三方应用访问其私有资源,而无需分享用户名和...
这里会用到Spring Security OAuth2的相关配置,如`AuthorizationServerConfigurerAdapter`。 2. **资源服务器配置**:其他微服务作为资源服务器,需要配置Spring Security以接受OAuth2.0的令牌进行授权。这通常涉及...
《SpringCloud OAuth2 Security:构建安全微服务架构》 OAuth2是目前广泛应用于授权和访问控制的开放标准,尤其在微服务架构中扮演着至关重要的角色。Spring Cloud Security结合了OAuth2,为开发者提供了一套便捷的...
"Spring Cloud与OAuth2整合简介" Spring Cloud与OAuth2整合简介是指将Spring Cloud框架与OAuth2认证协议进行整合,以实现微服务架构中的身份验证和授权管理。OAuth2是目前最流行的身份验证和授权协议之一,广泛应用...
Spring Cloud OAuth2 是一个基于 Spring Boot 和 Spring Security 的授权服务器实现,它为微服务架构提供了安全的认证和授权服务。这个案例将深入讲解如何在实际项目中应用 OAuth2,以确保用户数据的安全,并允许第...