<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                目前很多開放平臺如新浪微博開放平臺都在使用提供開放 API 接口供開發者使用,隨之帶來了第三方應用要到開放平臺進行授權的問題,OAuth 就是干這個的,OAuth2 是 OAuth 協議的下一個版本,相比 OAuth1,OAuth2 整個授權流程更簡單安全了,但不兼容 OAuth1,具體可以到 OAuth2 官網 [](http://oauth.net/2/)<http://oauth.net/2/> 查看,OAuth2 協議規范可以參考 [](http://tools.ietf.org/html/rfc6749)<http://tools.ietf.org/html/rfc6749>。目前有好多參考實現供選擇,可以到其官網查看下載。 本文使用 [Apache Oltu](),其之前的名字叫 Apache Amber ,是 Java 版的參考實現。使用文檔可參考 [](https://cwiki.apache.org/confluence/display/OLTU/Documentation)<https://cwiki.apache.org/confluence/display/OLTU/Documentation>。 **OAuth 角色** **資源擁有者(resource owner)**:能授權訪問受保護資源的一個實體,可以是一個人,那我們稱之為最終用戶;如新浪微博用戶 zhangsan; **資源服務器(resource server)**:存儲受保護資源,客戶端通過 access token 請求資源,資源服務器響應受保護資源給客戶端;存儲著用戶 zhangsan 的微博等信息。 **授權服務器(authorization server)**:成功驗證資源擁有者并獲取授權之后,授權服務器頒發授權令牌(Access Token)給客戶端。 **客戶端(client)**:如新浪微博客戶端 weico、微格等第三方應用,也可以是它自己的官方應用;其本身不存儲資源,而是資源擁有者授權通過后,使用它的授權(授權令牌)訪問受保護資源,然后客戶端把相應的數據展示出來 / 提交到服務器。“客戶端” 術語不代表任何特定實現(如應用運行在一臺服務器、桌面、手機或其他設備)。 ![](https://box.kancloud.cn/9c18230bd83c9c63705bee1c94bd256e_368x257.png) 1. 客戶端從資源擁有者那請求授權。授權請求可以直接發給資源擁有者,或間接的通過授權服務器這種中介,后者更可取。 2. 客戶端收到一個授權許可,代表資源服務器提供的授權。 3. 客戶端使用它自己的私有證書及授權許可到授權服務器驗證。 4. 如果驗證成功,則下發一個訪問令牌。 5. 客戶端使用訪問令牌向資源服務器請求受保護資源。 6. 資源服務器會驗證訪問令牌的有效性,如果成功則下發受保護資源。 更多流程的解釋請參考 OAuth2 的協議規范 [](http://tools.ietf.org/html/rfc6749)<http://tools.ietf.org/html/rfc6749>。 ## 17.1 服務器端 本文把授權服務器和資源服務器整合在一起實現。 **POM 依賴** 此處我們使用 apache oltu oauth2 服務端實現,需要引入 authzserver(授權服務器依賴)和 resourceserver(資源服務器依賴)。 ``` xml <dependency> <groupId>org.apache.oltu.oauth2</groupId> <artifactId>org.apache.oltu.oauth2.authzserver</artifactId> <version>0.31</version> </dependency> <dependency> <groupId>org.apache.oltu.oauth2</groupId> <artifactId>org.apache.oltu.oauth2.resourceserver</artifactId> <version>0.31</version> </dependency> ``` 其他的請參考 pom.xml。 **數據字典** 用戶 (oauth2\_user) | 名稱 | 類型 | 長度 | 描述 | | --- | --- | --- | --- | | id | bigint | | 編號 主鍵 | | username | varchar | 100 | 用戶名 | | password | varchar | 100 | 密碼 | | salt | varchar | 50 | 鹽 | 客戶端 (oauth2\_client) | 名稱 | 類型 | 長度 | 描述 | | --- | --- | --- | --- | | id | bigint | | 編號 主鍵 | | client_name | varchar | 100 | 客戶端名稱 | | client_id | varchar | 100 | 客戶端 id | | client_secret | varchar | 100 | 客戶端安全 key | 用戶表存儲著認證 / 資源服務器的用戶信息,即資源擁有者;比如用戶名 / 密碼;客戶端表存儲客戶端的的客戶端 id 及客戶端安全 key;在進行授權時使用。 **表及數據 SQL** 具體請參考 * sql/ shiro-schema.sql (表結構) * sql/ shiro-data.sql (初始數據) 默認用戶名 / 密碼是 admin/123456。 **實體** 具體請參考 com.github.zhangkaitao.shiro.chapter17.entity 包下的實體,此處就不列舉了。 **DAO** 具體請參考 com.github.zhangkaitao.shiro.chapter17.dao 包下的 DAO 接口及實現。 **Service** 具體請參考 com.github.zhangkaitao.shiro.chapter17.service 包下的 Service 接口及實現。以下是出了基本 CRUD 之外的關鍵接口: ``` java public interface UserService { public User createUser(User user);// 創建用戶 public User updateUser(User user);// 更新用戶 public void deleteUser(Long userId);// 刪除用戶 public void changePassword(Long userId, String newPassword); //修改密碼 User findOne(Long userId);// 根據id查找用戶 List<User> findAll();// 得到所有用戶 public User findByUsername(String username);// 根據用戶名查找用戶 } ``` ``` java public interface ClientService { public Client createClient(Client client);// 創建客戶端 public Client updateClient(Client client);// 更新客戶端 public void deleteClient(Long clientId);// 刪除客戶端 Client findOne(Long clientId);// 根據id查找客戶端 List<Client> findAll();// 查找所有 Client findByClientId(String clientId);// 根據客戶端id查找客戶端 Client findByClientSecret(String clientSecret);//根據客戶端安全KEY查找客戶端 } ``` ``` java public interface OAuthService { public void addAuthCode(String authCode, String username);// 添加 auth code public void addAccessToken(String accessToken, String username); // 添加 access token boolean checkAuthCode(String authCode); // 驗證auth code是否有效 boolean checkAccessToken(String accessToken); // 驗證access token是否有效 String getUsernameByAuthCode(String authCode);// 根據auth code獲取用戶名 String getUsernameByAccessToken(String accessToken);// 根據access token獲取用戶名 long getExpireIn();//auth code / access token 過期時間 public boolean checkClientId(String clientId);// 檢查客戶端id是否存在 public boolean checkClientSecret(String clientSecret);// 堅持客戶端安全KEY是否存在 } ``` 此處通過 OAuthService 實現進行 auth code 和 access token 的維護。 **后端數據維護控制器** 具體請參考 com.github.zhangkaitao.shiro.chapter17.web.controller 包下的 IndexController、LoginController、UserController 和 ClientController,其用于維護后端的數據,如用戶及客戶端數據;即相當于后臺管理。 **授權控制器 AuthorizeController** ``` @Controller public class AuthorizeController { @Autowired private OAuthService oAuthService; @Autowired private ClientService clientService; @RequestMapping("/authorize") public Object authorize(Model model, HttpServletRequest request) throws URISyntaxException, OAuthSystemException { try { //構建OAuth 授權請求 OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(request); //檢查傳入的客戶端id是否正確 if (!oAuthService.checkClientId(oauthRequest.getClientId())) { OAuthResponse response = OAuthASResponse .errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_CLIENT) .setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION) .buildJSONMessage(); return new ResponseEntity( response.getBody(), HttpStatus.valueOf(response.getResponseStatus())); } Subject subject = SecurityUtils.getSubject(); //如果用戶沒有登錄,跳轉到登陸頁面 if(!subject.isAuthenticated()) { if(!login(subject, request)) {//登錄失敗時跳轉到登陸頁面 model.addAttribute("client", clientService.findByClientId(oauthRequest.getClientId())); return "oauth2login"; } } String username = (String)subject.getPrincipal(); //生成授權碼 String authorizationCode = null; //responseType目前僅支持CODE,另外還有TOKEN String responseType = oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE); if (responseType.equals(ResponseType.CODE.toString())) { OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator()); authorizationCode = oauthIssuerImpl.authorizationCode(); oAuthService.addAuthCode(authorizationCode, username); } //進行OAuth響應構建 OAuthASResponse.OAuthAuthorizationResponseBuilder builder = OAuthASResponse.authorizationResponse(request, HttpServletResponse.SC_FOUND); //設置授權碼 builder.setCode(authorizationCode); //得到到客戶端重定向地址 String redirectURI = oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI); //構建響應 final OAuthResponse response = builder.location(redirectURI).buildQueryMessage(); //根據OAuthResponse返回ResponseEntity響應 HttpHeaders headers = new HttpHeaders(); headers.setLocation(new URI(response.getLocationUri())); return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus())); } catch (OAuthProblemException e) { //出錯處理 String redirectUri = e.getRedirectUri(); if (OAuthUtils.isEmpty(redirectUri)) { //告訴客戶端沒有傳入redirectUri直接報錯 return new ResponseEntity( "OAuth callback url needs to be provided by client!!!", HttpStatus.NOT_FOUND); } //返回錯誤消息(如?error=) final OAuthResponse response = OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND) .error(e).location(redirectUri).buildQueryMessage(); HttpHeaders headers = new HttpHeaders(); headers.setLocation(new URI(response.getLocationUri())); return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus())); } } private boolean login(Subject subject, HttpServletRequest request) { if("get".equalsIgnoreCase(request.getMethod())) { return false; } String username = request.getParameter("username"); String password = request.getParameter("password"); if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) { return false; } UsernamePasswordToken token = new UsernamePasswordToken(username, password); try { subject.login(token); return true; } catch (Exception e) { request.setAttribute("error", "登錄失敗:" + e.getClass().getName()); return false; } } } ``` 如上代碼的作用: 1. 首先通過如 `http://localhost:8080/chapter17-server/authorize?client_id=c1ebe466-1cdc-4bd3-ab69-77c3561b9dee&response_type=code&redirect_uri=http://localhost:9080/chapter17-client/oauth2-login` 訪問授權頁面; 2. 該控制器首先檢查 clientId 是否正確;如果錯誤將返回相應的錯誤信息; 3. 然后判斷用戶是否登錄了,如果沒有登錄首先到登錄頁面登錄; 4. 登錄成功后生成相應的 auth code 即授權碼,然后重定向到客戶端地址,如 `http://localhost:9080/chapter17-client/oauth2-login?code=52b1832f5dff68122f4f00ae995da0ed`;在重定向到的地址中會帶上 code 參數(授權碼),接著客戶端可以根據授權碼去換取 access token。 **訪問令牌控制器 AccessTokenController** ``` java @RestController public class AccessTokenController { @Autowired private OAuthService oAuthService; @Autowired private UserService userService; @RequestMapping("/accessToken") public HttpEntity token(HttpServletRequest request) throws URISyntaxException, OAuthSystemException { try { //構建OAuth請求 OAuthTokenRequest oauthRequest = new OAuthTokenRequest(request); //檢查提交的客戶端id是否正確 if (!oAuthService.checkClientId(oauthRequest.getClientId())) { OAuthResponse response = OAuthASResponse .errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_CLIENT) .setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION) .buildJSONMessage(); return new ResponseEntity( response.getBody(), HttpStatus.valueOf(response.getResponseStatus())); } // 檢查客戶端安全KEY是否正確 if (!oAuthService.checkClientSecret(oauthRequest.getClientSecret())) { OAuthResponse response = OAuthASResponse .errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT) .setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION) .buildJSONMessage(); return new ResponseEntity( response.getBody(), HttpStatus.valueOf(response.getResponseStatus())); } String authCode = oauthRequest.getParam(OAuth.OAUTH_CODE); // 檢查驗證類型,此處只檢查AUTHORIZATION_CODE類型,其他的還有PASSWORD或REFRESH_TOKEN if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals( GrantType.AUTHORIZATION_CODE.toString())) { if (!oAuthService.checkAuthCode(authCode)) { OAuthResponse response = OAuthASResponse .errorResponse(HttpServletResponse.SC_BAD_REQUEST) .setError(OAuthError.TokenResponse.INVALID_GRANT) .setErrorDescription("錯誤的授權碼") .buildJSONMessage(); return new ResponseEntity( response.getBody(), HttpStatus.valueOf(response.getResponseStatus())); } } //生成Access Token OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator()); final String accessToken = oauthIssuerImpl.accessToken(); oAuthService.addAccessToken(accessToken, oAuthService.getUsernameByAuthCode(authCode)); //生成OAuth響應 OAuthResponse response = OAuthASResponse .tokenResponse(HttpServletResponse.SC_OK) .setAccessToken(accessToken) .setExpiresIn(String.valueOf(oAuthService.getExpireIn())) .buildJSONMessage(); //根據OAuthResponse生成ResponseEntity return new ResponseEntity( response.getBody(), HttpStatus.valueOf(response.getResponseStatus())); } catch (OAuthProblemException e) { //構建錯誤響應 OAuthResponse res = OAuthASResponse .errorResponse(HttpServletResponse.SC_BAD_REQUEST).error(e) .buildJSONMessage(); return new ResponseEntity(res.getBody(), HttpStatus.valueOf(res.getResponseStatus())); } } } ``` 如上代碼的作用: 1. 首先通過如 `http://localhost:8080/chapter17-server/accessToken`,POST 提交如下數據: ``` client_id= c1ebe466-1cdc-4bd3-ab69-77c3561b9dee& client_secret= d8346ea2-6017-43ed-ad68-19c0f971738b&grant_type=authorization_code&code=828beda907066d058584f37bcfd597b6&redirect_uri=http://localhost:9080/chapter17-client/oauth2-login ``` 訪問; 2. 該控制器會驗證 client\_id、client\_secret、auth code 的正確性,如果錯誤會返回相應的錯誤; 3. 如果驗證通過會生成并返回相應的訪問令牌 access token。 **資源控制器 UserInfoController** ``` java @RestController public class UserInfoController { @Autowired private OAuthService oAuthService; @RequestMapping("/userInfo") public HttpEntity userInfo(HttpServletRequest request) throws OAuthSystemException { try { //構建OAuth資源請求 OAuthAccessResourceRequest oauthRequest = new OAuthAccessResourceRequest(request, ParameterStyle.QUERY); //獲取Access Token String accessToken = oauthRequest.getAccessToken(); //驗證Access Token if (!oAuthService.checkAccessToken(accessToken)) { // 如果不存在/過期了,返回未驗證錯誤,需重新驗證 OAuthResponse oauthResponse = OAuthRSResponse .errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setRealm(Constants.RESOURCE_SERVER_NAME) .setError(OAuthError.ResourceResponse.INVALID_TOKEN) .buildHeaderMessage(); HttpHeaders headers = new HttpHeaders(); headers.add(OAuth.HeaderType.WWW_AUTHENTICATE, oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE)); return new ResponseEntity(headers, HttpStatus.UNAUTHORIZED); } //返回用戶名 String username = oAuthService.getUsernameByAccessToken(accessToken); return new ResponseEntity(username, HttpStatus.OK); } catch (OAuthProblemException e) { //檢查是否設置了錯誤碼 String errorCode = e.getError(); if (OAuthUtils.isEmpty(errorCode)) { OAuthResponse oauthResponse = OAuthRSResponse .errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setRealm(Constants.RESOURCE_SERVER_NAME) .buildHeaderMessage(); HttpHeaders headers = new HttpHeaders(); headers.add(OAuth.HeaderType.WWW_AUTHENTICATE, oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE)); return new ResponseEntity(headers, HttpStatus.UNAUTHORIZED); } OAuthResponse oauthResponse = OAuthRSResponse .errorResponse(HttpServletResponse.SC_UNAUTHORIZED) .setRealm(Constants.RESOURCE_SERVER_NAME) .setError(e.getError()) .setErrorDescription(e.getDescription()) .setErrorUri(e.getUri()) .buildHeaderMessage(); HttpHeaders headers = new HttpHeaders(); headers.add(OAuth.HeaderType.WWW_AUTHENTICATE, 、 oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE)); return new ResponseEntity(HttpStatus.BAD_REQUEST); } } } ``` 如上代碼的作用: 1. 首先通過如 `http://localhost:8080/chapter17-server/userInfo? access_token=828beda907066d058584f37bcfd597b6` 進行訪問; 2. 該控制器會驗證 access token 的有效性;如果無效了將返回相應的錯誤,客戶端再重新進行授權; 3. 如果有效,則返回當前登錄用戶的用戶名。 **Spring 配置文件** 具體請參考 `resources/spring*.xml`,此處只列舉 spring-config-shiro.xml 中的 shiroFilter 的 filterChainDefinitions 屬性: ``` xml <property name="filterChainDefinitions"> <value> / = anon /login = authc /logout = logout /authorize=anon /accessToken=anon /userInfo=anon /** = user </value> </property> ``` 對于 oauth2 的幾個地址 /authorize、/accessToken、/userInfo 都是匿名可訪問的。 其他源碼請直接下載文檔查看。 **服務器維護** 訪問 `localhost:8080/chapter17-server/`,登錄后進行客戶端管理和用戶管理。 客戶端管理就是進行客戶端的注冊,如新浪微博的第三方應用就需要到新浪微博開發平臺進行注冊;用戶管理就是進行如新浪微博用戶的管理。 對于授權服務和資源服務的實現可以參考新浪微博開發平臺的實現: * [](http://open.weibo.com/wiki)<http://open.weibo.com/wiki> / 授權機制說明 * [](http://open.weibo.com/wiki/)<http://open.weibo.com/wiki/> 微博 API ## 17.2 客戶端 客戶端流程:如果需要登錄首先跳到 oauth2 服務端進行登錄授權,成功后服務端返回 auth code,然后客戶端使用 auth code 去服務器端換取 access token,最好根據 access token 獲取用戶信息進行客戶端的登錄綁定。這個可以參照如很多網站的新浪微博登錄功能,或其他的第三方帳號登錄功能。 **POM 依賴** 此處我們使用 apache oltu oauth2 客戶端實現。 ``` xml <dependency> <groupId>org.apache.oltu.oauth2</groupId> <artifactId>org.apache.oltu.oauth2.client</artifactId> <version>0.31</version> </dependency> ``` 其他的請參考 pom.xml。 **OAuth2Token** 類似于 UsernamePasswordToken 和 CasToken;用于存儲 oauth2 服務端返回的 auth code。 ``` java public class OAuth2Token implements AuthenticationToken { private String authCode; private String principal; public OAuth2Token(String authCode) { this.authCode = authCode; } //省略getter/setter } ``` **OAuth2AuthenticationFilter** 該 filter 的作用類似于 FormAuthenticationFilter 用于 oauth2 客戶端的身份驗證控制;如果當前用戶還沒有身份驗證,首先會判斷 url 中是否有 code(服務端返回的 auth code),如果沒有則重定向到服務端進行登錄并授權,然后返回 auth code;接著 OAuth2AuthenticationFilter 會用 auth code 創建 OAuth2Token,然后提交給 Subject.login 進行登錄;接著 OAuth2Realm 會根據 OAuth2Token 進行相應的登錄邏輯。 ``` java public class OAuth2AuthenticationFilter extends AuthenticatingFilter { //oauth2 authc code參數名 private String authcCodeParam = "code"; //客戶端id private String clientId; //服務器端登錄成功/失敗后重定向到的客戶端地址 private String redirectUrl; //oauth2服務器響應類型 private String responseType = "code"; private String failureUrl; //省略setter protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception { HttpServletRequest httpRequest = (HttpServletRequest) request; String code = httpRequest.getParameter(authcCodeParam); return new OAuth2Token(code); } protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) { return false; } protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { String error = request.getParameter("error"); String errorDescription = request.getParameter("error_description"); if(!StringUtils.isEmpty(error)) {//如果服務端返回了錯誤 WebUtils.issueRedirect(request, response, failureUrl + "?error=" + error + "error_description=" + errorDescription); return false; } Subject subject = getSubject(request, response); if(!subject.isAuthenticated()) { if(StringUtils.isEmpty(request.getParameter(authcCodeParam))) { //如果用戶沒有身份驗證,且沒有auth code,則重定向到服務端授權 saveRequestAndRedirectToLogin(request, response); return false; } } //執行父類里的登錄邏輯,調用Subject.login登錄 return executeLogin(request, response); } //登錄成功后的回調方法 重定向到成功頁面 protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request, ServletResponse response) throws Exception { issueSuccessRedirect(request, response); return false; } //登錄失敗后的回調 protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request, ServletResponse response) { Subject subject = getSubject(request, response); if (subject.isAuthenticated() || subject.isRemembered()) { try { //如果身份驗證成功了 則也重定向到成功頁面 issueSuccessRedirect(request, response); } catch (Exception e) { e.printStackTrace(); } } else { try { //登錄失敗時重定向到失敗頁面 WebUtils.issueRedirect(request, response, failureUrl); } catch (IOException e) { e.printStackTrace(); } } return false; } } ``` 該攔截器的作用: 1. 首先判斷有沒有服務端返回的 error 參數,如果有則直接重定向到失敗頁面; 2. 接著如果用戶還沒有身份驗證,判斷是否有 auth code 參數(即是不是服務端授權之后返回的),如果沒有則重定向到服務端進行授權; 3. 否則調用 executeLogin 進行登錄,通過 auth code 創建 OAuth2Token 提交給 Subject 進行登錄; 4. 登錄成功將回調 onLoginSuccess 方法重定向到成功頁面; 5. 登錄失敗則回調 onLoginFailure 重定向到失敗頁面。 **OAuth2Realm** ``` java public class OAuth2Realm extends AuthorizingRealm { private String clientId; private String clientSecret; private String accessTokenUrl; private String userInfoUrl; private String redirectUrl; //省略setter public boolean supports(AuthenticationToken token) { return token instanceof OAuth2Token; //表示此Realm只支持OAuth2Token類型 } protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); return authorizationInfo; } protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { OAuth2Token oAuth2Token = (OAuth2Token) token; String code = oAuth2Token.getAuthCode(); //獲取 auth code String username = extractUsername(code); // 提取用戶名 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(username, code, getName()); return authenticationInfo; } private String extractUsername(String code) { try { OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient()); OAuthClientRequest accessTokenRequest = OAuthClientRequest .tokenLocation(accessTokenUrl) .setGrantType(GrantType.AUTHORIZATION_CODE) .setClientId(clientId).setClientSecret(clientSecret) .setCode(code).setRedirectURI(redirectUrl) .buildQueryMessage(); //獲取access token OAuthAccessTokenResponse oAuthResponse = oAuthClient.accessToken(accessTokenRequest, OAuth.HttpMethod.POST); String accessToken = oAuthResponse.getAccessToken(); Long expiresIn = oAuthResponse.getExpiresIn(); //獲取user info OAuthClientRequest userInfoRequest = new OAuthBearerClientRequest(userInfoUrl) .setAccessToken(accessToken).buildQueryMessage(); OAuthResourceResponse resourceResponse = oAuthClient.resource( userInfoRequest, OAuth.HttpMethod.GET, OAuthResourceResponse.class); String username = resourceResponse.getBody(); return username; } catch (Exception e) { throw new OAuth2AuthenticationException(e); } } } ``` 此 Realm 首先只支持 OAuth2Token 類型的 Token;然后通過傳入的 auth code 去換取 access token;再根據 access token 去獲取用戶信息(用戶名),然后根據此信息創建 AuthenticationInfo;如果需要 AuthorizationInfo 信息,可以根據此處獲取的用戶名再根據自己的業務規則去獲取。 **Spring shiro 配置(spring-config-shiro.xml)** ``` xml <bean id="oAuth2Realm" class="com.github.zhangkaitao.shiro.chapter18.oauth2.OAuth2Realm"> <property name="cachingEnabled" value="true"/> <property name="authenticationCachingEnabled" value="true"/> <property name="authenticationCacheName" value="authenticationCache"/> <property name="authorizationCachingEnabled" value="true"/> <property name="authorizationCacheName" value="authorizationCache"/> <property name="clientId" value="c1ebe466-1cdc-4bd3-ab69-77c3561b9dee"/> <property name="clientSecret" value="d8346ea2-6017-43ed-ad68-19c0f971738b"/> <property name="accessTokenUrl" value="http://localhost:8080/chapter17-server/accessToken"/> <property name="userInfoUrl" value="http://localhost:8080/chapter17-server/userInfo"/> <property name="redirectUrl" value="http://localhost:9080/chapter17-client/oauth2-login"/> </bean> ``` 此 OAuth2Realm 需要配置在服務端申請的 clientId 和 clientSecret;及用于根據 auth code 換取 access token 的 accessTokenUrl 地址;及用于根據 access token 換取用戶信息(受保護資源)的 userInfoUrl 地址。 ``` xml <bean id="oAuth2AuthenticationFilter" class="com.github.zhangkaitao.shiro.chapter18.oauth2.OAuth2AuthenticationFilter"> <property name="authcCodeParam" value="code"/> <property name="failureUrl" value="/oauth2Failure.jsp"/> </bean> ``` 此 OAuth2AuthenticationFilter 用于攔截服務端重定向回來的 auth code。 ``` xml <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager"/> <property name="loginUrl" value="http://localhost:8080/chapter17-server/authorize?client_id=c1ebe466-1cdc-4bd3-ab69-77c3561b9dee&amp;response_type=code&amp;redirect_uri=http://localhost:9080/chapter17-client/oauth2-login"/> <property name="successUrl" value="/"/> <property name="filters"> <util:map> <entry key="oauth2Authc" value-ref="oAuth2AuthenticationFilter"/> </util:map> </property> <property name="filterChainDefinitions"> <value> / = anon /oauth2Failure.jsp = anon /oauth2-login = oauth2Authc /logout = logout /** = user </value> </property> </bean> ``` 此處設置 loginUrl 為 `http://localhost:8080/chapter17-server/authorize?client_id=c1ebe466-1cdc-4bd3-ab69-77c3561b9dee&amp;response_type=code&amp;redirect_uri=http://localhost:9080/chapter17-client/oauth2-login"`;其會自動設置到所有的 AccessControlFilter,如 oAuth2AuthenticationFilter;另外 /oauth2-login = oauth2Authc 表示 /oauth2-login 地址使用 oauth2Authc 攔截器攔截并進行 oauth2 客戶端授權。 **測試** 1、首先訪問 `http://localhost:9080/chapter17-client/`,然后點擊登錄按鈕進行登錄,會跳到如下頁面: ![](https://box.kancloud.cn/2a68a4aa0f83b14df8594f7473969d4e_651x108.png) 2、輸入用戶名進行登錄并授權; 3、如果登錄成功,服務端會重定向到客戶端,即之前客戶端提供的地址 `http://localhost:9080/chapter17-client/oauth2-login?code=473d56015bcf576f2ca03eac1a5bcc11`,并帶著 auth code 過去; 4、客戶端的 OAuth2AuthenticationFilter 會收集此 auth code,并創建 OAuth2Token 提交給 Subject 進行客戶端登錄; 5、客戶端的 Subject 會委托給 OAuth2Realm 進行身份驗證;此時 OAuth2Realm 會根據 auth code 換取 access token,再根據 access token 獲取受保護的用戶信息;然后進行客戶端登錄。 到此 OAuth2 的集成就完成了,此處的服務端和客戶端相對比較簡單,沒有進行一些異常檢測,請參考如新浪微博進行相應 API 及異常錯誤碼的設計。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看