Spring Security OAuth2.0 token生成与刷新机制源码阅读
一.介绍
Spring Security Oauth2是目前市面上非常流行的实现了OAuth2.0协议的权限框架。本文会介绍其是如何获取token
以及刷新token
的。
二.AbstractEndPoint
Spring Security OAuth2的获取token、校验token等接口均配置在EndPoint
中的
AuthorizationEndpoint
主要是第三方授权模式中的 获取code
的流程接口http://localhost:xxxx/auth/oauth/authorize
TokenEndpoint
主要是获取token/刷新token的接口http://localhost:xxxx/auth/oauth/token
2.1 AuthorizationEndpoint
AuthorizationEndpoint
主要是获取第三方授权模式中的code
,核心代码如下
@RequestMapping(value = "/oauth/authorize", method = RequestMethod.POST, params = OAuth2Utils.USER_OAUTH_APPROVAL)public View approveOrDeny(@RequestParam Map<String, String> approvalParameters, Map<String, ?> model,SessionStatus sessionStatus, Principal principal) {if (!(principal instanceof Authentication)) {sessionStatus.setComplete();throw new InsufficientAuthenticationException("User must be authenticated with Spring Security before authorizing an access token.");}AuthorizationRequest authorizationRequest = (AuthorizationRequest) model.get("authorizationRequest");if (authorizationRequest == null) {//清空session缓存sessionStatus.setComplete();throw new InvalidRequestException("Cannot approve uninitialized authorization request.");}try {Set<String> responseTypes = authorizationRequest.getResponseTypes();authorizationRequest.setApprovalParameters(approvalParameters);authorizationRequest = userApprovalHandler.updateAfterApproval(authorizationRequest,(Authentication) principal);boolean approved = userApprovalHandler.isApproved(authorizationRequest, (Authentication) principal);authorizationRequest.setApproved(approved);if (authorizationRequest.getRedirectUri() == null) {sessionStatus.setComplete();throw new InvalidRequestException("Cannot approve request when no redirect URI is provided.");}if (!authorizationRequest.isApproved()) {return new RedirectView(getUnsuccessfulRedirect(authorizationRequest,new UserDeniedAuthorizationException("User denied access"), responseTypes.contains("token")),false, true, false);}if (responseTypes.contains("token")) {return getImplicitGrantResponse(authorizationRequest).getView();}//生成code并跳转到传递过来的redirect_uri上return getAuthorizationCodeResponse(authorizationRequest, (Authentication) principal);}finally {sessionStatus.setComplete();}}//生成viewprivate View getAuthorizationCodeResponse(AuthorizationRequest authorizationRequest, Authentication authUser) {try {return new RedirectView(getSuccessfulRedirect(authorizationRequest,generateCode(authorizationRequest, authUser)), false, true, false);}catch (OAuth2Exception e) {return new RedirectView(getUnsuccessfulRedirect(authorizationRequest, e, false), false, true, false);}}//生成codeprivate String generateCode(AuthorizationRequest authorizationRequest, Authentication authentication)throws AuthenticationException {try {OAuth2Request storedOAuth2Request = getOAuth2RequestFactory().createOAuth2Request(authorizationRequest);OAuth2Authentication combinedAuth = new OAuth2Authentication(storedOAuth2Request, authentication);String code = authorizationCodeServices.createAuthorizationCode(combinedAuth);return code;}catch (OAuth2Exception e) {if (authorizationRequest.getState() != null) {e.addAdditionalInformation("state", authorizationRequest.getState());}throw e;}}
2.2 TokenEndPoint
@RequestMapping(value = "/oauth/token", method=RequestMethod.POST)public ResponseEntity<OAuth2AccessToken> postAccessToken(Principal principal, @RequestParamMap<String, String> parameters) throws HttpRequestMethodNotSupportedException {if (!(principal instanceof Authentication)) {throw new InsufficientAuthenticationException("There is no client authentication. Try adding an appropriate authentication filter.");}//校验clientIdString clientId = getClientId(principal);ClientDetails authenticatedClient = getClientDetailsService().loadClientByClientId(clientId);TokenRequest tokenRequest = getOAuth2RequestFactory().createTokenRequest(parameters, authenticatedClient);if (clientId != null && !clientId.equals("")) {// Only validate the client details if a client authenticated during this// request.if (!clientId.equals(tokenRequest.getClientId())) {// double check to make sure that the client ID in the token request is the same as that in the// authenticated clientthrow new InvalidClientException("Given client ID does not match authenticated client");}}if (authenticatedClient != null) {oAuth2RequestValidator.validateScope(tokenRequest, authenticatedClient);}//校验grantTypeif (!StringUtils.hasText(tokenRequest.getGrantType())) {throw new InvalidRequestException("Missing grant type");}if (tokenRequest.getGrantType().equals("implicit")) {//implicit模式不支持token获取throw new InvalidGrantException("Implicit grant type not supported from token endpoint");}//判断是否是第三方授权模式if (isAuthCodeRequest(parameters)) {// The scope was requested or determined during the authorization stepif (!tokenRequest.getScope().isEmpty()) {logger.debug("Clearing scope of incoming token request");tokenRequest.setScope(Collections.<String> emptySet());}}//判断是否是refresh_token模式if (isRefreshTokenRequest(parameters)) {// A refresh token has its own default scopes, so we should ignore any added by the factory here.tokenRequest.setScope(OAuth2Utils.parseParameterList(parameters.get(OAuth2Utils.SCOPE)));}//适配器模式,通过grant_type匹配对应的Granter,创建access_token并返回OAuth2AccessToken token = getTokenGranter().grant(tokenRequest.getGrantType(), tokenRequest);if (token == null) {throw new UnsupportedGrantTypeException("Unsupported grant type: " + tokenRequest.getGrantType());}return getResponse(token);}/*** @param principal the currently authentication principal* @return a client id if there is one in the principal*/protected String getClientId(Principal principal) {Authentication client = (Authentication) principal;if (!client.isAuthenticated()) {throw new InsufficientAuthenticationException("The client is not authenticated.");}String clientId = client.getName();if (client instanceof OAuth2Authentication) {// Might be a client and user combined authenticationclientId = ((OAuth2Authentication) client).getOAuth2Request().getClientId();}return clientId;}private ResponseEntity<OAuth2AccessToken> getResponse(OAuth2AccessToken accessToken) {HttpHeaders headers = new HttpHeaders();headers.set("Cache-Control", "no-store");headers.set("Pragma", "no-cache");headers.set("Content-Type", "application/json;charset=UTF-8");return new ResponseEntity<OAuth2AccessToken>(accessToken, headers, HttpStatus.OK);}private boolean isRefreshTokenRequest(Map<String, String> parameters) {return "refresh_token".equals(parameters.get("grant_type")) && parameters.get("refresh_token") != null;}private boolean isAuthCodeRequest(Map<String, String> parameters) {return "authorization_code".equals(parameters.get("grant_type")) && parameters.get("code") != null;}
三.TokenGranter
在TokenGranter
接口下定义了各种模式,核心方法是grant
,执行此方法可以获得OAuth2AccessToken
3.1 CompositeTokenGranter
Spring Security OAuth2
默认使用的是CompositeTokenGranter
private TokenGranter tokenGranter() {//如果tokenGranter没有设置,则默认使用的 CompositeTokenGranter ,但本质上还是使用的AbstractTokenGranter的几个子类if (tokenGranter == null) {tokenGranter = new TokenGranter() {private CompositeTokenGranter delegate;@Overridepublic OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {if (delegate == null) {delegate = new CompositeTokenGranter(getDefaultTokenGranters());}return delegate.grant(grantType, tokenRequest);}};}return tokenGranter;}//获取默认的几种模式,不过只包括第三方授权、efresh_code、客户端、密码这四个模式private List<TokenGranter> getDefaultTokenGranters() {ClientDetailsService clientDetails = clientDetailsService();AuthorizationServerTokenServices tokenServices = tokenServices();AuthorizationCodeServices authorizationCodeServices = authorizationCodeServices();OAuth2RequestFactory requestFactory = requestFactory();List<TokenGranter> tokenGranters = new ArrayList<TokenGranter>();tokenGranters.add(new AuthorizationCodeTokenGranter(tokenServices, authorizationCodeServices, clientDetails,requestFactory));tokenGranters.add(new RefreshTokenGranter(tokenServices, clientDetails, requestFactory));ImplicitTokenGranter implicit = new ImplicitTokenGranter(tokenServices, clientDetails, requestFactory);tokenGranters.add(implicit);tokenGranters.add(new ClientCredentialsTokenGranter(tokenServices, clientDetails, requestFactory));if (authenticationManager != null) {tokenGranters.add(new ResourceOwnerPasswordTokenGranter(authenticationManager, tokenServices,clientDetails, requestFactory));}return tokenGranters;}
public class CompositeTokenGranter implements TokenGranter {private final List<TokenGranter> tokenGranters;public CompositeTokenGranter(List<TokenGranter> tokenGranters) {this.tokenGranters = new ArrayList<TokenGranter>(tokenGranters);}public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {//遍历各个granter并匹配grantTypefor (TokenGranter granter : tokenGranters) {OAuth2AccessToken grant = granter.grant(grantType, tokenRequest);if (grant!=null) {return grant;}}return null;}public void addTokenGranter(TokenGranter tokenGranter) {if (tokenGranter == null) {throw new IllegalArgumentException("Token granter is null");}tokenGranters.add(tokenGranter);}}
3.2 AbstractTokenGranter
这个抽象类是我们上面所说的几种模式的父类,在继承TokenGranter
的同时,抽取了各个模式的共有方法和代码
public abstract class AbstractTokenGranter implements TokenGranter {protected final Log logger = LogFactory.getLog(getClass());private final AuthorizationServerTokenServices tokenServices;private final ClientDetailsService clientDetailsService;private final OAuth2RequestFactory requestFactory;private final String grantType;protected AbstractTokenGranter(AuthorizationServerTokenServices tokenServices,ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {this.clientDetailsService = clientDetailsService;this.grantType = grantType;this.tokenServices = tokenServices;this.requestFactory = requestFactory;}//实现tokenGranter接口的方法public OAuth2AccessToken grant(String grantType, TokenRequest tokenRequest) {//匹配grant_typeif (!this.grantType.equals(grantType)) {return null;}String clientId = tokenRequest.getClientId();ClientDetails client = clientDetailsService.loadClientByClientId(clientId);//判断账号权限validateGrantType(grantType, client);if (logger.isDebugEnabled()) {logger.debug("Getting access token for: " + clientId);}//获取access_tokenreturn getAccessToken(client, tokenRequest);}//获取access_token方法,可以看到,此处获取access_token的本质是创建access_token,意味着每次获取都会创建一个新的access_token//tokenServices的解析见下面的 DefaultTokenServicesprotected OAuth2AccessToken getAccessToken(ClientDetails client, TokenRequest tokenRequest) {return tokenServices.createAccessToken(getOAuth2Authentication(client, tokenRequest));}protected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {OAuth2Request storedOAuth2Request = requestFactory.createOAuth2Request(client, tokenRequest);return new OAuth2Authentication(storedOAuth2Request, null);}protected void validateGrantType(String grantType, ClientDetails clientDetails) {Collection<String> authorizedGrantTypes = clientDetails.getAuthorizedGrantTypes();if (authorizedGrantTypes != null && !authorizedGrantTypes.isEmpty()&& !authorizedGrantTypes.contains(grantType)) {throw new InvalidClientException("Unauthorized grant type: " + grantType);}}protected AuthorizationServerTokenServices getTokenServices() {return tokenServices;}protected OAuth2RequestFactory getRequestFactory() {return requestFactory;}}
3.3 DefaultTokenServices
public class DefaultTokenServices implements AuthorizationServerTokenServices, ResourceServerTokenServices,ConsumerTokenServices, InitializingBean {private int refreshTokenValiditySeconds = 60 * 60 * 24 * 30; // default 30 days.private int accessTokenValiditySeconds = 60 * 60 * 12; // default 12 hours.//判断是否支持refreshTokenprivate boolean supportRefreshToken = false;//刷新(即重新生成)access_token时,是否需要刷新(即重新生成)refresh_tokenprivate boolean reuseRefreshToken = true;private TokenStore tokenStore;private ClientDetailsService clientDetailsService;private TokenEnhancer accessTokenEnhancer;private AuthenticationManager authenticationManager;/*** Initialize these token services. If no random generator is set, one will be created.*/public void afterPropertiesSet() throws Exception {Assert.notNull(tokenStore, "tokenStore must be set");}@Transactionalpublic OAuth2AccessToken createAccessToken(OAuth2Authentication authentication) throws AuthenticationException {//取出存储的access_tokenOAuth2AccessToken existingAccessToken = tokenStore.getAccessToken(authentication);OAuth2RefreshToken refreshToken = null;if (existingAccessToken != null) {if (existingAccessToken.isExpired()) {//存储的有access_token但已过期,从存储中删除此access_token,如果此access_token有对应的refresh_token,则从存储中删除此refresh_tokenif (existingAccessToken.getRefreshToken() != null) {refreshToken = existingAccessToken.getRefreshToken();// The token store could remove the refresh token when the// access token is removed, but we want to// be sure...tokenStore.removeRefreshToken(refreshToken);}tokenStore.removeAccessToken(existingAccessToken);}else {// Re-store the access token in case the authentication has changed//存储的有access_token,并且未过期,存储后直接返回即可tokenStore.storeAccessToken(existingAccessToken, authentication);return existingAccessToken;}}// Only create a new refresh token if there wasn't an existing one// associated with an expired access token.// Clients might be holding existing refresh tokens, so we re-use it in// the case that the old access token// expired.if (refreshToken == null) {refreshToken = createRefreshToken(authentication);}// But the refresh token itself might need to be re-issued if it has// expired.else if (refreshToken instanceof ExpiringOAuth2RefreshToken) {ExpiringOAuth2RefreshToken expiring = (ExpiringOAuth2RefreshToken) refreshToken;if (System.currentTimeMillis() > expiring.getExpiration().getTime()) {//如果refresh_token已过期,创建新的refreshToken = createRefreshToken(authentication);}}OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);tokenStore.storeAccessToken(accessToken, authentication);// In case it was modifiedrefreshToken = accessToken.getRefreshToken();if (refreshToken != null) {tokenStore.storeRefreshToken(refreshToken, authentication);}return accessToken;}@Transactional(noRollbackFor={InvalidTokenException.class, InvalidGrantException.class})public OAuth2AccessToken refreshAccessToken(String refreshTokenValue, TokenRequest tokenRequest)throws AuthenticationException {//如果不支持刷新token,则直接报错if (!supportRefreshToken) {throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);}OAuth2RefreshToken refreshToken = tokenStore.readRefreshToken(refreshTokenValue);if (refreshToken == null) {throw new InvalidGrantException("Invalid refresh token: " + refreshTokenValue);}OAuth2Authentication authentication = tokenStore.readAuthenticationForRefreshToken(refreshToken);if (this.authenticationManager != null && !authentication.isClientOnly()) {// The client has already been authenticated, but the user authentication might be old now, so give it a// chance to re-authenticate.Authentication user = new PreAuthenticatedAuthenticationToken(authentication.getUserAuthentication(), "", authentication.getAuthorities());user = authenticationManager.authenticate(user);Object details = authentication.getDetails();authentication = new OAuth2Authentication(authentication.getOAuth2Request(), user);authentication.setDetails(details);}String clientId = authentication.getOAuth2Request().getClientId();if (clientId == null || !clientId.equals(tokenRequest.getClientId())) {throw new InvalidGrantException("Wrong client for this refresh token: " + refreshTokenValue);}// clear out any access tokens already associated with the refresh// token.tokenStore.removeAccessTokenUsingRefreshToken(refreshToken);if (isExpired(refreshToken)) {tokenStore.removeRefreshToken(refreshToken);throw new InvalidTokenException("Invalid refresh token (expired): " + refreshToken);}authentication = createRefreshedAuthentication(authentication, tokenRequest);//设定的规则是刷新access_token时一起刷新refresh_tokenif (!reuseRefreshToken) {tokenStore.removeRefreshToken(refreshToken);refreshToken = createRefreshToken(authentication);}OAuth2AccessToken accessToken = createAccessToken(authentication, refreshToken);tokenStore.storeAccessToken(accessToken, authentication);if (!reuseRefreshToken) {tokenStore.storeRefreshToken(accessToken.getRefreshToken(), authentication);}return accessToken;}}
3.4 AuthorizationCodeTokenGranter
public class AuthorizationCodeTokenGranter extends AbstractTokenGranter {private static final String GRANT_TYPE = "authorization_code";private final AuthorizationCodeServices authorizationCodeServices;public AuthorizationCodeTokenGranter(AuthorizationServerTokenServices tokenServices,AuthorizationCodeServices authorizationCodeServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {this(tokenServices, authorizationCodeServices, clientDetailsService, requestFactory, GRANT_TYPE);}protected AuthorizationCodeTokenGranter(AuthorizationServerTokenServices tokenServices, AuthorizationCodeServices authorizationCodeServices,ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {super(tokenServices, clientDetailsService, requestFactory, grantType);this.authorizationCodeServices = authorizationCodeServices;}@Overrideprotected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {Map<String, String> parameters = tokenRequest.getRequestParameters();//获取codeString authorizationCode = parameters.get("code");String redirectUri = parameters.get(OAuth2Utils.REDIRECT_URI);if (authorizationCode == null) {throw new InvalidRequestException("An authorization code must be supplied.");}//删除并判断code是否合法(这也是为什么code只能使用一次的原因)OAuth2Authentication storedAuth = authorizationCodeServices.consumeAuthorizationCode(authorizationCode);if (storedAuth == null) {throw new InvalidGrantException("Invalid authorization code: " + authorizationCode);}OAuth2Request pendingOAuth2Request = storedAuth.getOAuth2Request();// https://jira.springsource.org/browse/SECOAUTH-333// This might be null, if the authorization was done without the redirect_uri parameterString redirectUriApprovalParameter = pendingOAuth2Request.getRequestParameters().get(OAuth2Utils.REDIRECT_URI);//校验重定向uri是否跟获取code时传递的一样。if ((redirectUri != null || redirectUriApprovalParameter != null)&& !pendingOAuth2Request.getRedirectUri().equals(redirectUri)) {throw new RedirectMismatchException("Redirect URI mismatch.");}//校验clientIdString pendingClientId = pendingOAuth2Request.getClientId();String clientId = tokenRequest.getClientId();if (clientId != null && !clientId.equals(pendingClientId)) {// just a sanity check.throw new InvalidClientException("Client ID mismatch");}// Secret is not required in the authorization request, so it won't be available// in the pendingAuthorizationRequest. We do want to check that a secret is provided// in the token request, but that happens elsewhere.//封装用户信息并存储、返回Map<String, String> combinedParameters = new HashMap<String, String>(pendingOAuth2Request.getRequestParameters());// Combine the parameters adding the new ones last so they override if there are any clashescombinedParameters.putAll(parameters);// Make a new stored request with the combined parametersOAuth2Request finalStoredOAuth2Request = pendingOAuth2Request.createOAuth2Request(combinedParameters);Authentication userAuth = storedAuth.getUserAuthentication();return new OAuth2Authentication(finalStoredOAuth2Request, userAuth);}}
3.5 ResourceOwnerPasswordTokenGranter
public class ResourceOwnerPasswordTokenGranter extends AbstractTokenGranter {private static final String GRANT_TYPE = "password";private final AuthenticationManager authenticationManager;public ResourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager,AuthorizationServerTokenServices tokenServices, ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory) {this(authenticationManager, tokenServices, clientDetailsService, requestFactory, GRANT_TYPE);}protected ResourceOwnerPasswordTokenGranter(AuthenticationManager authenticationManager, AuthorizationServerTokenServices tokenServices,ClientDetailsService clientDetailsService, OAuth2RequestFactory requestFactory, String grantType) {super(tokenServices, clientDetailsService, requestFactory, grantType);this.authenticationManager = authenticationManager;}@Overrideprotected OAuth2Authentication getOAuth2Authentication(ClientDetails client, TokenRequest tokenRequest) {Map<String, String> parameters = new LinkedHashMap<String, String>(tokenRequest.getRequestParameters());String username = parameters.get("username");String password = parameters.get("password");// Protect from downstream leaks of passwordparameters.remove("password");Authentication userAuth = new UsernamePasswordAuthenticationToken(username, password);((AbstractAuthenticationToken) userAuth).setDetails(parameters);try {//根据用户名和密码获取用户信息(带校验功能)userAuth = authenticationManager.authenticate(userAuth);}catch (AccountStatusException ase) {//covers expired, locked, disabled cases (mentioned in section 5.2, draft 31)throw new InvalidGrantException(ase.getMessage());}catch (BadCredentialsException e) {// If the username/password are wrong the spec says we should send 400/invalid grantthrow new InvalidGrantException(e.getMessage());}if (userAuth == null || !userAuth.isAuthenticated()) {throw new InvalidGrantException("Could not authenticate user: " + username);}OAuth2Request storedOAuth2Request = getRequestFactory().createOAuth2Request(client, tokenRequest); return new OAuth2Authentication(storedOAuth2Request, userAuth);}
}
四.总结
通过阅读Spring Security OAuth2
的源码我们发现
Spring Security OAuth2
的获取token
部分主要是使用适配器模式,通过接口传递的grant_type
来判断具体的模式,通过抽象类的公有方法及子类的覆盖方法完成token
的获取与刷新Spring Security OAuth2
的token
获取与刷新是同一个接口,刷新时,refresh_token
可以配置成一起刷新或不刷新。如果refresh_token
会在access_token
刷新时一起刷新的话,那么就可以实现如果用户持续访问系统,便可以一直保持登录,无须再次登录的功能Spring Security OAth2
的token
刷新策略实质上是重新生成新的token
返回,而不是我们理解的把原来的token
的过期时长进行延长
Spring Security OAuth2.0 token生成与刷新机制源码阅读相关推荐
- Spring Security系列(32)- Spring Security Oauth2之authorities授权使用详解及源码分析
前言 在oauth_client_details表中,有一个authorities字段,从字面上来看是授权的意思,在之前我们分析了可以通过resourceId和scope进行授权,那么这个author ...
- Spring Security OAuth2.0认证授权知识概括
Spring Security OAuth2.0认证授权知识概括 安全框架基本概念 基于Session的认证方式 Spring Security简介 SpringSecurity详解 分布式系统认证方 ...
- Spring Security + OAuth2.0
授权服务器 授权服务器中有4个端点.说明如下: Authorize Endpoint :授权端点,进行授权. Token Endpoint :令牌端点,经过授权拿到对应的Token. lntrospe ...
- Spring Security OAuth2.0认证授权五:用户信息扩展到jwt
历史文章 [Spring Security OAuth2.0认证授权一:框架搭建和认证测试] [Spring Security OAuth2.0认证授权二:搭建资源服务] [Spring Securi ...
- Spring Security OAuth2.0认证授权三:使用JWT令牌
历史文章 [Spring Security OAuth2.0认证授权一:框架搭建和认证测试] [Spring Security OAuth2.0认证授权二:搭建资源服务] 前面两篇文章详细讲解了如何基 ...
- spring security oauth2.0 实现
oauth应该属于security的一部分.关于oauth的的相关知识可以查看阮一峰的文章:http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html ...
- Spring Security oauth2.0微信小程序登录
微信小程序前期开发准备,可以参考这篇文章微信小程序前期准备 1.学习过Spring Secrity oauth2.0的都知道,他有四种登录模式可以选择 authorization code(授权码模式 ...
- 从零开始超详细的Spring Security OAuth2.0实现分布式系统授权(注册中心+网关+认证授权服务(JWT令牌验证)+资源调用服务)
文章目录 一.OAuth2.0 1.介绍 2.例子 3.执行流程 二.Spring Cloud Security OAuth2 1.环境介绍 2.认证流程 三.整合分布式项目 1.技术方案 2.项目结 ...
- Spring Security OAuth2.0认证授权
文章目录 1.基本概念 1.1.什么是认证 1.2 什么是会话 1.3什么是授权 1.4授权的数据模型 1.4 RBAC 1.4.1 基于角色的访问控制 2.基于Session的认证方式 3.整合案例 ...
最新文章
- WOJ 1313 - K尾相等数
- java字符串 n换行符_java切割字符串中的回车应注意是\n\r不是\n
- 2021-02-21 Python Easyocr 图片文字识别
- WinForm中的各种对话框
- 沙洋有几个微服务群_集群 分布式 微服务
- 转:如何在 LoadRunner 脚本中做关联 (Correlation)
- Java JUC工具类--ForkJoin
- Arcgis Javascript那些事儿(四)--feature access服务编辑feature本质
- 紧急!Log4j2 再再爆雷:刚升级,又连爆 “核弹级” 远程数据泄露 ! v2.17.0 横空出世。。。...
- jxls设置隐藏列隐藏行
- 解决quartus与modelsim-se以及modelsim-altera同时使用导致的仿真失败问题
- 增强现实将为我们展示美好的未来还是使我们盲目
- 《初等数论及其应用》第三章 素数和最大公因子
- Mac 系统升级ssh报错
- 处nm是什么意思_“nm”是什么意思啊?
- 中国电子学会2022年12月份青少年软件编程Python等级考试试卷三级真题(含答案)
- 游戏产业迎新机遇,KuPlay平台助力多元化发展
- week6 day4 并发编程之多线程 理论
- camera调试名词及问题策略
- uniapp引用外部在线js