1. 程式人生 > >OAuth2整合——《跟我學Shiro》

OAuth2整合——《跟我學Shiro》

http://jinnianshilongnian.iteye.com/blog/2038646

目前很多開放平臺如新浪微博開放平臺都在使用提供開放API介面供開發者使用,隨之帶來了第三方應用要到開放平臺進行授權的問題,OAuth就是幹這個的,OAuth2是OAuth協議的下一個版本,相比OAuth1,OAuth2整個授權流程更簡單安全了,但不相容OAuth1,具體可以到OAuth2官網http://oauth.net/2/檢視,OAuth2協議規範可以參考http://tools.ietf.org/html/rfc6749。目前有好多參考實現供選擇,可以到其官網檢視下載。

OAuth角色

資源擁有者(resource owner)

:能授權訪問受保護資源的一個實體,可以是一個人,那我們稱之為終端使用者;如新浪微博使用者zhangsan;

資源伺服器(resource server):儲存受保護資源,客戶端通過access token請求資源,資源伺服器響應受保護資源給客戶端;儲存著使用者zhangsan的微博等資訊。

授權伺服器(authorization server):成功驗證資源擁有者並獲取授權之後,授權伺服器頒發授權令牌(Access Token)給客戶端。

客戶端(client):如新浪微部落格戶端weico、微格等第三方應用,也可以是它自己的官方應用;其本身不儲存資源,而是資源擁有者授權通過後,使用它的授權(授權令牌)訪問受保護資源,然後客戶端把相應的資料展示出來/提交到伺服器。“客戶端”術語不代表任何特定實現(如應用執行在一臺伺服器、桌面、手機或其他裝置)。 

OAuth2協議流程


 

1、客戶端從資源擁有者那請求授權。授權請求可以直接發給資源擁有者,或間接的通過授權伺服器這種中介,後者更可取。

2、客戶端收到一個授權許可,代表資源伺服器提供的授權。

3、客戶端使用它自己的私有證書及授權許可到授權伺服器驗證。

4、如果驗證成功,則下發一個訪問令牌。

5、客戶端使用訪問令牌向資源伺服器請求受保護資源。

6、資源伺服器會驗證訪問令牌的有效性,如果成功則下發受保護資源。

伺服器端

本文把授權伺服器和資源伺服器整合在一起實現。

POM依賴

此處我們使用apache oltu oauth2服務端實現,需要引入authzserver(授權伺服器依賴)和resourceserver(資源伺服器依賴)。 

Java程式碼  收藏程式碼
  1. <dependency>  
  2.     <groupId>org.apache.oltu.oauth2</groupId>  
  3.     <artifactId>org.apache.oltu.oauth2.authzserver</artifactId>  
  4.     <version>0.31</version>  
  5. </dependency>  
  6. <dependency>  
  7.     <groupId>org.apache.oltu.oauth2</groupId>  
  8.     <artifactId>org.apache.oltu.oauth2.resourceserver</artifactId>  
  9.     <version>0.31</version>  
  10. </dependency>   

其他的請參考pom.xml。

資料字典

使用者(oauth2_user)

名稱

型別

長度

描述

id

bigint

10

編號 主鍵

username

varchar

100

使用者名稱

password

varchar

100

密碼

salt

varchar

50

客戶端(oauth2_client)

名稱

型別

長度

描述

id

bigint

10

編號 主鍵

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程式碼  收藏程式碼
  1. public interface UserService {  
  2.     public User createUser(User user);// 建立使用者  
  3.     public User updateUser(User user);// 更新使用者  
  4.     public void deleteUser(Long userId);// 刪除使用者  
  5.     public void changePassword(Long userId, String newPassword); //修改密碼  
  6.     User findOne(Long userId);// 根據id查詢使用者  
  7.     List<User> findAll();// 得到所有使用者  
  8.     public User findByUsername(String username);// 根據使用者名稱查詢使用者  
  9. }  
Java程式碼  收藏程式碼
  1. public interface ClientService {  
  2.     public Client createClient(Client client);// 建立客戶端  
  3.     public Client updateClient(Client client);// 更新客戶端  
  4.     public void deleteClient(Long clientId);// 刪除客戶端  
  5.     Client findOne(Long clientId);// 根據id查詢客戶端  
  6.     List<Client> findAll();// 查詢所有  
  7.     Client findByClientId(String clientId);// 根據客戶端id查詢客戶端  
  8.     Client findByClientSecret(String clientSecret);//根據客戶端安全KEY查詢客戶端  
  9. }  
Java程式碼  收藏程式碼
  1. public interface OAuthService {  
  2.    public void addAuthCode(String authCode, String username);// 新增 auth code  
  3.    public void addAccessToken(String accessToken, String username); // 新增 access token  
  4.    boolean checkAuthCode(String authCode); // 驗證auth code是否有效  
  5.    boolean checkAccessToken(String accessToken); // 驗證access token是否有效  
  6.    String getUsernameByAuthCode(String authCode);// 根據auth code獲取使用者名稱  
  7.    String getUsernameByAccessToken(String accessToken);// 根據access token獲取使用者名稱  
  8.    long getExpireIn();//auth code / access token 過期時間  
  9.    public boolean checkClientId(String clientId);// 檢查客戶端id是否存在  
  10.    public boolean checkClientSecret(String clientSecret);// 堅持客戶端安全KEY是否存在  
  11. }   

此處通過OAuthService實現進行auth code和access token的維護。

後端資料維護控制器

具體請參考com.github.zhangkaitao.shiro.chapter17.web.controller包下的IndexController、LoginController、UserController和ClientController,其用於維護後端的資料,如使用者及客戶端資料;即相當於後臺管理。

授權控制器AuthorizeController      

Java程式碼  收藏程式碼
  1. @Controller  
  2. public class AuthorizeController {  
  3.   @Autowired  
  4.   private OAuthService oAuthService;  
  5.   @Autowired  
  6.   private ClientService clientService;  
  7.   @RequestMapping("/authorize")  
  8.   public Object authorize(Model model,  HttpServletRequest request)  
  9.         throws URISyntaxException, OAuthSystemException {  
  10.     try {  
  11.       //構建OAuth 授權請求  
  12.       OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(request);  
  13.       //檢查傳入的客戶端id是否正確  
  14.       if (!oAuthService.checkClientId(oauthRequest.getClientId())) {  
  15.         OAuthResponse response = OAuthASResponse  
  16.              .errorResponse(HttpServletResponse.SC_BAD_REQUEST)  
  17.              .setError(OAuthError.TokenResponse.INVALID_CLIENT)  
  18.              .setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)  
  19.              .buildJSONMessage();  
  20.         return new ResponseEntity(  
  21.            response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));  
  22.       }  
  23.       Subject subject = SecurityUtils.getSubject();  
  24.       //如果使用者沒有登入,跳轉到登陸頁面  
  25.       if(!subject.isAuthenticated()) {  
  26.         if(!login(subject, request)) {//登入失敗時跳轉到登陸頁面  
  27.           model.addAttribute("client",      
  28.               clientService.findByClientId(oauthRequest.getClientId()));  
  29.           return "oauth2login";  
  30.         }  
  31.       }  
  32.       String username = (String)subject.getPrincipal();  
  33.       //生成授權碼  
  34.       String authorizationCode = null;  
  35.       //responseType目前僅支援CODE,另外還有TOKEN  
  36.       String responseType = oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE);  
  37.       if (responseType.equals(ResponseType.CODE.toString())) {  
  38.         OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());  
  39.         authorizationCode = oauthIssuerImpl.authorizationCode();  
  40.         oAuthService.addAuthCode(authorizationCode, username);  
  41.       }  
  42.       //進行OAuth響應構建  
  43.       OAuthASResponse.OAuthAuthorizationResponseBuilder builder =  
  44.         OAuthASResponse.authorizationResponse(request,   
  45.                                            HttpServletResponse.SC_FOUND);  
  46.       //設定授權碼  
  47.       builder.setCode(authorizationCode);  
  48.       //得到到客戶端重定向地址  
  49.       String redirectURI = oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI);  
  50.       //構建響應  
  51.       final OAuthResponse response = builder.location(redirectURI).buildQueryMessage();  
  52.       //根據OAuthResponse返回ResponseEntity響應  
  53.       HttpHeaders headers = new HttpHeaders();  
  54.       headers.setLocation(new URI(response.getLocationUri()));  
  55.       return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus()));  
  56.     } catch (OAuthProblemException e) {  
  57.       //出錯處理  
  58.       String redirectUri = e.getRedirectUri();  
  59.       if (OAuthUtils.isEmpty(redirectUri)) {  
  60.         //告訴客戶端沒有傳入redirectUri直接報錯  
  61.         return new ResponseEntity(  
  62.           "OAuth callback url needs to be provided by client!!!", HttpStatus.NOT_FOUND);  
  63.       }  
  64.       //返回錯誤訊息(如?error=)  
  65.       final OAuthResponse response =  
  66.               OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND)  
  67.                       .error(e).location(redirectUri).buildQueryMessage();  
  68.       HttpHeaders headers = new HttpHeaders();  
  69.       headers.setLocation(new URI(response.getLocationUri()));  
  70.       return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus()));  
  71.     }  
  72.   }  
  73.   private boolean login(Subject subject, HttpServletRequest request) {  
  74.     if("get".equalsIgnoreCase(request.getMethod())) {  
  75.       return false;  
  76.     }  
  77.     String username = request.getParameter("username");  
  78.     String password = request.getParameter("password");  
  79.     if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {  
  80.       return false;  
  81.     }  
  82.     UsernamePasswordToken token = new UsernamePasswordToken(username, password);  
  83.     try {  
  84.       subject.login(token);  
  85.       return true;  
  86.     } catch (Exception e) {  
  87.       request.setAttribute("error""登入失敗:" + e.getClass().getName());  
  88.       return false;  
  89.     }  
  90.   }  
  91. }   

如上程式碼的作用:

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程式碼  收藏程式碼
  1. @RestController  
  2. public class AccessTokenController {  
  3.   @Autowired  
  4.   private OAuthService oAuthService;  
  5.   @Autowired  
  6.   private UserService userService;  
  7.   @RequestMapping("/accessToken")  
  8.   public HttpEntity token(HttpServletRequest request)  
  9.           throws URISyntaxException, OAuthSystemException {  
  10.     try {  
  11.       //構建OAuth請求  
  12.       OAuthTokenRequest oauthRequest = new OAuthTokenRequest(request);  
  13.       //檢查提交的客戶端id是否正確  
  14.       if (!oAuthService.checkClientId(oauthRequest.getClientId())) {  
  15.         OAuthResponse response = OAuthASResponse  
  16.                 .errorResponse(HttpServletResponse.SC_BAD_REQUEST)  
  17.                 .setError(OAuthError.TokenResponse.INVALID_CLIENT)  
  18.                 .setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)  
  19.                 .buildJSONMessage();  
  20.        return new ResponseEntity(  
  21.          response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));  
  22.       }  
  23.     // 檢查客戶端安全KEY是否正確  
  24.       if (!oAuthService.checkClientSecret(oauthRequest.getClientSecret())) {  
  25.         OAuthResponse response = OAuthASResponse  
  26.               .errorResponse(HttpServletResponse.SC_UNAUTHORIZED)  
  27.               .setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)  
  28.               .setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)  
  29.               .buildJSONMessage();  
  30.       return new ResponseEntity(  
  31.           response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));  
  32.       }  
  33.       String authCode = oauthRequest.getParam(OAuth.OAUTH_CODE);  
  34.       // 檢查驗證型別,此處只檢查AUTHORIZATION_CODE型別,其他的還有PASSWORD或REFRESH_TOKEN  
  35.       if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(  
  36.          GrantType.AUTHORIZATION_CODE.toString())) {  
  37.          if (!oAuthService.checkAuthCode(authCode)) {  
  38.             OAuthResponse response = OAuthASResponse  
  39.                 .errorResponse(HttpServletResponse.SC_BAD_REQUEST)  
  40.                 .setError(OAuthError.TokenResponse.INVALID_GRANT)  
  41.                 .setErrorDescription("錯誤的授權碼")  
  42.               .buildJSONMessage();  
  43.            return new ResponseEntity(  
  44.              response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));  
  45.          }  
  46.       }  
  47.       //生成Access Token  
  48.       OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());  
  49.       final String accessToken = oauthIssuerImpl.accessToken();  
  50.       oAuthService.addAccessToken(accessToken,  
  51.           oAuthService.getUsernameByAuthCode(authCode));  
  52.       //生成OAuth響應  
  53.       OAuthResponse response = OAuthASResponse  
  54.               .tokenResponse(HttpServletResponse.SC_OK)  
  55.               .setAccessToken(accessToken)  
  56.               .setExpiresIn(String.valueOf(oAuthService.getExpireIn()))  
  57.               .buildJSONMessage();  
  58.       //根據OAuthResponse生成ResponseEntity  
  59.       return new ResponseEntity(  
  60.           response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));  
  61.     } catch (OAuthProblemException e) {  
  62.       //構建錯誤響應  
  63.       OAuthResponse res = OAuthASResponse  
  64.               .errorResponse(HttpServletResponse.SC_BAD_REQUEST).error(e)  
  65.               .buildJSONMessage();  
  66.      return new ResponseEntity(res.getBody(), HttpStatus.valueOf(res.getResponseStatus()));  
  67.    }  
  68.  }  
  69. }   

如上程式碼的作用:

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程式碼  收藏程式碼
  1. @RestController  
  2. public class UserInfoController {  
  3.   @Autowired  
  4.   private OAuthService oAuthService;  
  5.   @RequestMapping("/userInfo")  
  6.   public HttpEntity userInfo(HttpServletRequest request) throws OAuthSystemException {  
  7.     try {  
  8.       //構建OAuth資源請求  
  9.       OAuthAccessResourceRequest oauthRequest =   
  10.             new OAuthAccessResourceRequest(request, ParameterStyle.QUERY);  
  11.       //獲取Access Token  
  12.       String accessToken = oauthRequest.getAccessToken();  
  13.       //驗證Access Token  
  14.       if (!oAuthService.checkAccessToken(accessToken)) {  
  15.         // 如果不存在/過期了,返回未驗證錯誤,需重新驗證  
  16.       OAuthResponse oauthResponse = OAuthRSResponse  
  17.               .errorResponse(HttpServletResponse.SC_UNAUTHORIZED)  
  18.               .setRealm(Constants.RESOURCE_SERVER_NAME)  
  19.               .setError(OAuthError.ResourceResponse.INVALID_TOKEN)  
  20.               .buildHeaderMessage();  
  21.         HttpHeaders headers = new HttpHeaders();  
  22.         headers.add(OAuth.HeaderType.WWW_AUTHENTICATE,   
  23.           oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE));  
  24.       return new ResponseEntity(headers, HttpStatus.UNAUTHORIZED);  
  25.       }  
  26.       //返回使用者名稱  
  27.       String username = oAuthService.getUsernameByAccessToken(accessToken);  
  28.       return new ResponseEntity(username, HttpStatus.OK);  
  29.     } catch (OAuthProblemException e) {  
  30.       //檢查是否設定了錯誤碼  
  31.       String errorCode = e.getError();  
  32.       if (OAuthUtils.isEmpty(errorCode)) {  
  33.         OAuthResponse oauthResponse = OAuthRSResponse  
  34.                .errorResponse(HttpServletResponse.SC_UNAUTHORIZED)  
  35.                .setRealm(Constants.RESOURCE_SERVER_NAME)  
  36.                .buildHeaderMessage();  
  37.         HttpHeaders headers = new HttpHeaders();  
  38.         headers.add(OAuth.HeaderType.WWW_AUTHENTICATE,   
  39.           oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE));  
  40.         return new ResponseEntity(headers, HttpStatus.UNAUTHORIZED);  
  41.       }  
  42.       OAuthResponse oauthResponse = OAuthRSResponse  
  43.                .errorResponse(HttpServletResponse.SC_UNAUTHORIZED)  
  44.                .setRealm(Constants.RESOURCE_SERVER_NAME)  
  45.                .setError(e.getError())  
  46.                .setErrorDescription(e.getDescription())  
  47.                .setErrorUri(e.getUri())  
  48.                .buildHeaderMessage();  
  49.       HttpHeaders headers = new HttpHeaders();  
  50.       headers.add(OAuth.HeaderType.WWW_AUTHENTICATE, 、  
  51.         oauthResponse.getHeader(OAuth.HeaderType.WWW_AUTHENTICATE));  
  52.       return new ResponseEntity(HttpStatus.BAD_REQUEST);  
  53.     }  
  54.   }  
  55. }   

如上程式碼的作用:

1、首先通過如http://localhost:8080/chapter17-server/userInfo? access_token=828beda907066d058584f37bcfd597b6進行訪問;

2、該控制器會驗證access token的有效性;如果無效了將返回相應的錯誤,客戶端再重新進行授權;

3、如果有效,則返回當前登入使用者的使用者名稱。

Spring配置檔案

具體請參考resources/spring*.xml,此處只列舉spring-config-shiro.xml中的shiroFilter的filterChainDefinitions屬性:  

Java程式碼  收藏程式碼
  1. <property name="filterChainDefinitions">  
  2.     <value>  
  3.       / = anon  
  4.       /login = authc  
  5.       /logout = logout  
  6.       /authorize=anon  
  7.       /accessToken=anon  
  8.       /userInfo=anon  
  9.       /** = user  
  10.     </value>  
  11. </property>   

對於oauth2的幾個地址/authorize、/accessToken、/userInfo都是匿名可訪問的。

其他原始碼請直接下載文件檢視。

伺服器維護

訪問localhost:8080/chapter17-server/,登入後進行客戶端管理和使用者管理。

客戶端管理就是進行客戶端的註冊,如新浪微博的第三方應用就需要到新浪微博開發平臺進行註冊;使用者管理就是進行如新浪微博使用者的管理。

對於授權服務和資源服務的實現可以參考新浪微博開發平臺的實現:

客戶端

客戶端流程:如果需要登入首先跳到oauth2服務端進行登入授權,成功後服務端返回auth code,然後客戶端使用auth code去伺服器端換取access token,最好根據access token獲取使用者資訊進行客戶端的登入繫結。這個可以參照如很多網站的新浪微博登入功能,或其他的第三方帳號登入功能。

POM依賴

此處我們使用apache oltu oauth2客戶端實現。     

Java程式碼  收藏程式碼
  1. <dependency>  
  2.   <groupId>org.apache.oltu.oauth2</groupId>  
  3.   <artifactId>org.apache.oltu.oauth2.client</artifactId>  
  4.   <version>0.31</version>  
  5. </dependency>   

其他的請參考pom.xml。

OAuth2Token

類似於UsernamePasswordToken和CasToken;用於儲存oauth2服務端返回的auth code。  

Java程式碼  收藏程式碼
  1. public class OAuth2Token implements AuthenticationToken {  
  2.     private String authCode;  
  3.     private String principal;  
  4.     public OAuth2Token(String authCode) {  
  5.         this.authCode = authCode;  
  6.     }  
  7.     //省略getter/setter  
  8. }   

OAuth2AuthenticationFilter

該filter的作用類似於FormAuthenticationFilter用於oauth2客戶端的身份驗證控制;如果當前使用者還沒有身份驗證,首先會判斷url中是否有code(服務端返回的auth code),如果沒有則重定向到服務端進行登入並授權,然後返回auth code;接著OAuth2AuthenticationFilter會用auth code建立OAuth2Token,然後提交給Subject.login進行登入;接著OAuth2Realm會根據OAuth2Token進行相應的登入邏輯。  

Java程式碼  收藏程式碼
  1. public class OAuth2AuthenticationFilter extends AuthenticatingFilter {  
  2.     //oauth2 authc code引數名  
  3.     private String authcCodeParam = "code";  
  4.     //客戶端id  
  5.     private String clientId;  
  6.     //伺服器端登入成功/失敗後重定向到的客戶端地址  
  7.     private String redirectUrl;  
  8.     //oauth2伺服器響應型別  
  9.     private String responseType = "code";  
  10.     private String failureUrl;  
  11.     //省略setter  
  12.     protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {  
  13.         HttpServletRequest httpRequest = (HttpServletRequest) request;  
  14.         String code = httpRequest.getParameter(authcCodeParam);  
  15.         return new OAuth2Token(code);  
  16.     }  
  17.     protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {  
  18.         return false;  
  19.     }  
  20.     protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {  
  21.         String error = request.getParameter("error");  
  22.         String errorDescription = request.getParameter("error_description");  
  23.         if(!StringUtils.isEmpty(error)) {//如果服務端返回了錯誤  
  24.             WebUtils.issueRedirect(request, response, failureUrl + "?error=" + error + "error_description=" + errorDescription);  
  25.             return false;  
  26.         }  
  27.         Subject subject = getSubject(request, response);  
  28.         if(!subject.isAuthenticated()) {  
  29.             if(StringUtils.isEmpty(request.getParameter(authcCodeParam))) {  
  30.                 //如果使用者沒有身份驗證,且沒有auth code,則重定向到服務端授權  
  31.                 saveRequestAndRedirectToLogin(request, response);  
  32.                 return false;  
  33.             }  
  34.         }  
  35.         //執行父類裡的登入邏輯,呼叫Subject.login登入  
  36.         return executeLogin(request, response);  
  37.     }  
  38.     //登入成功後的回撥方法 重定向到成功頁面  
  39.     protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,  ServletResponse response) throws Exception {  
  40.         issueSuccessRedirect(request, response);  
  41.         return false;  
  42.     }  
  43.     //登入失敗後的回撥   
  44.     protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request,  
  45.                                      ServletResponse response) {  
  46.         Subject subject = getSubject(request, response);  
  47.         if (subject.isAuthenticated() || subject.isRemembered()) {  
  48.             try { //如果身份驗證成功了 則也重定向到成功頁面  
  49.                 issueSuccessRedirect(request, response);  
  50.             } catch (Exception e) {  
  51.                 e.printStackTrace();  
  52.             }  
  53.         } else {  
  54.             try { //登入失敗時重定向到失敗頁面  
  55.                 WebUtils.issueRedirect(request, response, failureUrl);  
  56.             } catch (IOException e) {  
  57.                 e.printStackTrace();  
  58.             }  
  59.         }  
  60.         return false;  
  61.     }  
  62. }   

該攔截器的作用:

1、首先判斷有沒有服務端返回的error引數,如果有則直接重定向到失敗頁面;

2、接著如果使用者還沒有身份驗證,判斷是否有auth code引數(即是不是服務端授權之後返回的),如果沒有則重定向到服務端進行授權;

3、否則呼叫executeLogin進行登入,通過auth code建立OAuth2Token提交給Subject進行登入;

4、登入成功將回調onLoginSuccess方法重定向到成功頁面;

5、登入失敗則回撥onLoginFailure重定向到失敗頁面。

OAuth2Realm  

Java程式碼  收藏程式碼
  1. public class OAuth2Realm extends AuthorizingRealm {  
  2.     private String clientId;  
  3.     private String clientSecret;  
  4.     private String accessTokenUrl;  
  5.     private String userInfoUrl;  
  6.     private String redirectUrl;  
  7.     //省略setter  
  8.     public boolean supports(AuthenticationToken token) {  
  9.         return token instanceof OAuth2Token; //表示此Realm只支援OAuth2Token型別  
  10.     }  
  11.     protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {  
  12.         SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();  
  13.         return authorizationInfo;  
  14.     }  
  15.     protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {  
  16.         OAuth2Token oAuth2Token = (OAuth2Token) token;  
  17.         String code = oAuth2Token.getAuthCode(); //獲取 auth code  
  18.         String username = extractUsername(code); // 提取使用者名稱  
  19.         SimpleAuthenticationInfo authenticationInfo =  
  20.                 new SimpleAuthenticationInfo(username, code, getName());  
  21.         return authenticationInfo;  
  22.     }  
  23.     private String extractUsername(String code) {  
  24.         try {  
  25.             OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());  
  26.             OAuthClientRequest accessTokenRequest = OAuthClientRequest  
  27.                     .tokenLocation(accessTokenUrl)  
  28.                     .setGrantType(GrantType.AUTHORIZATION_CODE)  
  29.                     .setClientId(clientId).setClientSecret(clientSecret)  
  30.                     .setCode(code).setRedirectURI(redirectUrl)  
  31.                     .buildQueryMessage();  
  32.             //獲取access token  
  33.             OAuthAccessTokenResponse oAuthResponse =   
  34.                 oAuthClient.accessToken(accessTokenRequest, OAuth.HttpMethod.POST);  
  35.             String accessToken = oAuthResponse.getAccessToken();  
  36.             Long expiresIn = oAuthResponse.getExpiresIn();  
  37.             //獲取user info