免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
第十七章 OAuth2集成

 

目錄貼: 跟我學(xué)Shiro目錄貼

 

 

目前很多開放平臺(tái)如新浪微博開放平臺(tái)都在使用提供開放API接口供開發(fā)者使用,隨之帶來了第三方應(yīng)用要到開放平臺(tái)進(jìn)行授權(quán)的問題,OAuth就是干這個(gè)的,OAuth2OAuth協(xié)議的下一個(gè)版本,相比OAuth1OAuth2整個(gè)授權(quán)流程更簡單安全了,但不兼容OAuth1,具體可以到OAuth2官網(wǎng)http://oauth.net/2/查看,OAuth2協(xié)議規(guī)范可以參考http://tools.ietf.org/html/rfc6749。目前有好多參考實(shí)現(xiàn)供選擇,可以到其官網(wǎng)查看下載。

 

本文使用Apache Oltu,其之前的名字叫Apache Amber ,是Java版的參考實(shí)現(xiàn)。使用文檔可參考https://cwiki.apache.org/confluence/display/OLTU/Documentation。

 

OAuth角色

資源擁有者(resource owner:能授權(quán)訪問受保護(hù)資源的一個(gè)實(shí)體,可以是一個(gè)人,那我們稱之為最終用戶;如新浪微博用戶zhangsan;

資源服務(wù)器(resource server:存儲(chǔ)受保護(hù)資源,客戶端通過access token請(qǐng)求資源,資源服務(wù)器響應(yīng)受保護(hù)資源給客戶端;存儲(chǔ)著用戶zhangsan的微博等信息。

授權(quán)服務(wù)器(authorization server:成功驗(yàn)證資源擁有者并獲取授權(quán)之后,授權(quán)服務(wù)器頒發(fā)授權(quán)令牌(Access Token)給客戶端。

客戶端(client:如新浪微博客戶端weico、微格等第三方應(yīng)用,也可以是它自己的官方應(yīng)用;其本身不存儲(chǔ)資源,而是資源擁有者授權(quán)通過后,使用它的授權(quán)(授權(quán)令牌)訪問受保護(hù)資源,然后客戶端把相應(yīng)的數(shù)據(jù)展示出來/提交到服務(wù)器?!翱蛻舳恕毙g(shù)語不代表任何特定實(shí)現(xiàn)(如應(yīng)用運(yùn)行在一臺(tái)服務(wù)器、桌面、手機(jī)或其他設(shè)備)。 

 

OAuth2協(xié)議流程


 

1、客戶端從資源擁有者那請(qǐng)求授權(quán)。授權(quán)請(qǐng)求可以直接發(fā)給資源擁有者,或間接的通過授權(quán)服務(wù)器這種中介,后者更可取。

2、客戶端收到一個(gè)授權(quán)許可,代表資源服務(wù)器提供的授權(quán)。

3、客戶端使用它自己的私有證書及授權(quán)許可到授權(quán)服務(wù)器驗(yàn)證。

4、如果驗(yàn)證成功,則下發(fā)一個(gè)訪問令牌。

5、客戶端使用訪問令牌向資源服務(wù)器請(qǐng)求受保護(hù)資源。

6、資源服務(wù)器會(huì)驗(yàn)證訪問令牌的有效性,如果成功則下發(fā)受保護(hù)資源。

 

更多流程的解釋請(qǐng)參考OAuth2的協(xié)議規(guī)范http://tools.ietf.org/html/rfc6749。

 

服務(wù)器端

本文把授權(quán)服務(wù)器和資源服務(wù)器整合在一起實(shí)現(xiàn)。

 

POM依賴

此處我們使用apache oltu oauth2服務(wù)端實(shí)現(xiàn),需要引入authzserver(授權(quán)服務(wù)器依賴)和resourceserver(資源服務(wù)器依賴)。 

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>   

其他的請(qǐng)參考pom.xml。

 

數(shù)據(jù)字典

用戶(oauth2_user)

名稱

類型

長度

描述

id

bigint

10

編號(hào) 主鍵

username

varchar

100

用戶名

password

varchar

100

密碼

salt

varchar

50

客戶端(oauth2_client)

名稱

類型

長度

描述

id

bigint

10

編號(hào) 主鍵

client_name

varchar

100

客戶端名稱

client_id

varchar

100

客戶端id

client_secret

varchar

100

客戶端安全key

 

用戶表存儲(chǔ)著認(rèn)證/資源服務(wù)器的用戶信息,即資源擁有者;比如用戶名/密碼;客戶端表存儲(chǔ)客戶端的的客戶端id及客戶端安全key;在進(jìn)行授權(quán)時(shí)使用。

 

表及數(shù)據(jù)SQL

具體請(qǐng)參考

sql/ shiro-schema.sql (表結(jié)構(gòu))

sql/ shiro-data.sql  (初始數(shù)據(jù))

 

默認(rèn)用戶名/密碼是admin/123456。

 

實(shí)體

具體請(qǐng)參考com.github.zhangkaitao.shiro.chapter17.entity包下的實(shí)體,此處就不列舉了。

 

DAO

具體請(qǐng)參考com.github.zhangkaitao.shiro.chapter17.dao包下的DAO接口及實(shí)現(xiàn)。

 

Service

具體請(qǐng)參考com.github.zhangkaitao.shiro.chapter17.service包下的Service接口及實(shí)現(xiàn)。以下是出了基本CRUD之外的關(guān)鍵接口: 

Java代碼  
  1. public interface UserService {  
  2.     public User createUser(User user);// 創(chuàng)建用戶  
  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);// 根據(jù)id查找用戶  
  7.     List<User> findAll();// 得到所有用戶  
  8.     public User findByUsername(String username);// 根據(jù)用戶名查找用戶  
  9. }  
Java代碼  
  1. public interface ClientService {  
  2.     public Client createClient(Client client);// 創(chuàng)建客戶端  
  3.     public Client updateClient(Client client);// 更新客戶端  
  4.     public void deleteClient(Long clientId);// 刪除客戶端  
  5.     Client findOne(Long clientId);// 根據(jù)id查找客戶端  
  6.     List<Client> findAll();// 查找所有  
  7.     Client findByClientId(String clientId);// 根據(jù)客戶端id查找客戶端  
  8.     Client findByClientSecret(String clientSecret);//根據(jù)客戶端安全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); // 驗(yàn)證auth code是否有效  
  5.    boolean checkAccessToken(String accessToken); // 驗(yàn)證access token是否有效  
  6.    String getUsernameByAuthCode(String authCode);// 根據(jù)auth code獲取用戶名  
  7.    String getUsernameByAccessToken(String accessToken);// 根據(jù)access token獲取用戶名  
  8.    long getExpireIn();//auth code / access token 過期時(shí)間  
  9.    public boolean checkClientId(String clientId);// 檢查客戶端id是否存在  
  10.    public boolean checkClientSecret(String clientSecret);// 堅(jiān)持客戶端安全KEY是否存在  
  11. }   

此處通過OAuthService實(shí)現(xiàn)進(jìn)行auth codeaccess token的維護(hù)。

 

后端數(shù)據(jù)維護(hù)控制器

具體請(qǐng)參考com.github.zhangkaitao.shiro.chapter17.web.controller包下的IndexController、LoginControllerUserControllerClientController,其用于維護(hù)后端的數(shù)據(jù),如用戶及客戶端數(shù)據(jù);即相當(dāng)于后臺(tái)管理。

 

授權(quán)控制器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.       //構(gòu)建OAuth 授權(quán)請(qǐng)求  
  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.   
  24.       Subject subject = SecurityUtils.getSubject();  
  25.       //如果用戶沒有登錄,跳轉(zhuǎn)到登陸頁面  
  26.       if(!subject.isAuthenticated()) {  
  27.         if(!login(subject, request)) {//登錄失敗時(shí)跳轉(zhuǎn)到登陸頁面  
  28.           model.addAttribute("client",      
  29.               clientService.findByClientId(oauthRequest.getClientId()));  
  30.           return "oauth2login";  
  31.         }  
  32.       }  
  33.   
  34.       String username = (String)subject.getPrincipal();  
  35.       //生成授權(quán)碼  
  36.       String authorizationCode = null;  
  37.       //responseType目前僅支持CODE,另外還有TOKEN  
  38.       String responseType = oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE);  
  39.       if (responseType.equals(ResponseType.CODE.toString())) {  
  40.         OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());  
  41.         authorizationCode = oauthIssuerImpl.authorizationCode();  
  42.         oAuthService.addAuthCode(authorizationCode, username);  
  43.       }  
  44.       //進(jìn)行OAuth響應(yīng)構(gòu)建  
  45.       OAuthASResponse.OAuthAuthorizationResponseBuilder builder =  
  46.         OAuthASResponse.authorizationResponse(request,   
  47.                                            HttpServletResponse.SC_FOUND);  
  48.       //設(shè)置授權(quán)碼  
  49.       builder.setCode(authorizationCode);  
  50.       //得到到客戶端重定向地址  
  51.       String redirectURI = oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI);  
  52.   
  53.       //構(gòu)建響應(yīng)  
  54.       final OAuthResponse response = builder.location(redirectURI).buildQueryMessage();  
  55.       //根據(jù)OAuthResponse返回ResponseEntity響應(yīng)  
  56.       HttpHeaders headers = new HttpHeaders();  
  57.       headers.setLocation(new URI(response.getLocationUri()));  
  58.       return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus()));  
  59.     } catch (OAuthProblemException e) {  
  60.       //出錯(cuò)處理  
  61.       String redirectUri = e.getRedirectUri();  
  62.       if (OAuthUtils.isEmpty(redirectUri)) {  
  63.         //告訴客戶端沒有傳入redirectUri直接報(bào)錯(cuò)  
  64.         return new ResponseEntity(  
  65.           "OAuth callback url needs to be provided by client!!!", HttpStatus.NOT_FOUND);  
  66.       }  
  67.       //返回錯(cuò)誤消息(如?error=)  
  68.       final OAuthResponse response =  
  69.               OAuthASResponse.errorResponse(HttpServletResponse.SC_FOUND)  
  70.                       .error(e).location(redirectUri).buildQueryMessage();  
  71.       HttpHeaders headers = new HttpHeaders();  
  72.       headers.setLocation(new URI(response.getLocationUri()));  
  73.       return new ResponseEntity(headers, HttpStatus.valueOf(response.getResponseStatus()));  
  74.     }  
  75.   }  
  76.   
  77.   private boolean login(Subject subject, HttpServletRequest request) {  
  78.     if("get".equalsIgnoreCase(request.getMethod())) {  
  79.       return false;  
  80.     }  
  81.     String username = request.getParameter("username");  
  82.     String password = request.getParameter("password");  
  83.   
  84.     if(StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {  
  85.       return false;  
  86.     }  
  87.   
  88.     UsernamePasswordToken token = new UsernamePasswordToken(username, password);  
  89.     try {  
  90.       subject.login(token);  
  91.       return true;  
  92.     } catch (Exception e) {  
  93.       request.setAttribute("error", "登錄失敗:" + e.getClass().getName());  
  94.       return false;  
  95.     }  
  96.   }  
  97. }   

如上代碼的作用:

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訪問授權(quán)頁面;

2、該控制器首先檢查clientId是否正確;如果錯(cuò)誤將返回相應(yīng)的錯(cuò)誤信息;

3、然后判斷用戶是否登錄了,如果沒有登錄首先到登錄頁面登錄;

4、登錄成功后生成相應(yīng)的auth code即授權(quán)碼,然后重定向到客戶端地址,如http://localhost:9080/chapter17-client/oauth2-login?code=52b1832f5dff68122f4f00ae995da0ed;在重定向到的地址中會(huì)帶上code參數(shù)(授權(quán)碼),接著客戶端可以根據(jù)授權(quán)碼去換取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.       //構(gòu)建OAuth請(qǐng)求  
  12.       OAuthTokenRequest oauthRequest = new OAuthTokenRequest(request);  
  13.   
  14.       //檢查提交的客戶端id是否正確  
  15.       if (!oAuthService.checkClientId(oauthRequest.getClientId())) {  
  16.         OAuthResponse response = OAuthASResponse  
  17.                 .errorResponse(HttpServletResponse.SC_BAD_REQUEST)  
  18.                 .setError(OAuthError.TokenResponse.INVALID_CLIENT)  
  19.                 .setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)  
  20.                 .buildJSONMessage();  
  21.        return new ResponseEntity(  
  22.          response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));  
  23.       }  
  24.   
  25.     // 檢查客戶端安全KEY是否正確  
  26.       if (!oAuthService.checkClientSecret(oauthRequest.getClientSecret())) {  
  27.         OAuthResponse response = OAuthASResponse  
  28.               .errorResponse(HttpServletResponse.SC_UNAUTHORIZED)  
  29.               .setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)  
  30.               .setErrorDescription(Constants.INVALID_CLIENT_DESCRIPTION)  
  31.               .buildJSONMessage();  
  32.       return new ResponseEntity(  
  33.           response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));  
  34.       }  
  35.     
  36.       String authCode = oauthRequest.getParam(OAuth.OAUTH_CODE);  
  37.       // 檢查驗(yàn)證類型,此處只檢查AUTHORIZATION_CODE類型,其他的還有PASSWORD或REFRESH_TOKEN  
  38.       if (oauthRequest.getParam(OAuth.OAUTH_GRANT_TYPE).equals(  
  39.          GrantType.AUTHORIZATION_CODE.toString())) {  
  40.          if (!oAuthService.checkAuthCode(authCode)) {  
  41.             OAuthResponse response = OAuthASResponse  
  42.                 .errorResponse(HttpServletResponse.SC_BAD_REQUEST)  
  43.                 .setError(OAuthError.TokenResponse.INVALID_GRANT)  
  44.                 .setErrorDescription("錯(cuò)誤的授權(quán)碼")  
  45.               .buildJSONMessage();  
  46.            return new ResponseEntity(  
  47.              response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));  
  48.          }  
  49.       }  
  50.   
  51.       //生成Access Token  
  52.       OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());  
  53.       final String accessToken = oauthIssuerImpl.accessToken();  
  54.       oAuthService.addAccessToken(accessToken,  
  55.           oAuthService.getUsernameByAuthCode(authCode));  
  56.   
  57.       //生成OAuth響應(yīng)  
  58.       OAuthResponse response = OAuthASResponse  
  59.               .tokenResponse(HttpServletResponse.SC_OK)  
  60.               .setAccessToken(accessToken)  
  61.               .setExpiresIn(String.valueOf(oAuthService.getExpireIn()))  
  62.               .buildJSONMessage();  
  63.   
  64.       //根據(jù)OAuthResponse生成ResponseEntity  
  65.       return new ResponseEntity(  
  66.           response.getBody(), HttpStatus.valueOf(response.getResponseStatus()));  
  67.     } catch (OAuthProblemException e) {  
  68.       //構(gòu)建錯(cuò)誤響應(yīng)  
  69.       OAuthResponse res = OAuthASResponse  
  70.               .errorResponse(HttpServletResponse.SC_BAD_REQUEST).error(e)  
  71.               .buildJSONMessage();  
  72.      return new ResponseEntity(res.getBody(), HttpStatus.valueOf(res.getResponseStatus()));  
  73.    }  
  74.  }  
  75. }   

如上代碼的作用:

1、首先通過如http://localhost:8080/chapter17-server/accessToken,POST提交如下數(shù)據(jù):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、該控制器會(huì)驗(yàn)證client_id、client_secretauth code的正確性,如果錯(cuò)誤會(huì)返回相應(yīng)的錯(cuò)誤;

3、如果驗(yàn)證通過會(huì)生成并返回相應(yīng)的訪問令牌access token

 

資源控制器UserInfoController  

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

如上代碼的作用:

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

2、該控制器會(huì)驗(yàn)證access token的有效性;如果無效了將返回相應(yīng)的錯(cuò)誤,客戶端再重新進(jìn)行授權(quán);

3、如果有效,則返回當(dāng)前登錄用戶的用戶名。

 

Spring配置文件

具體請(qǐng)參考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.   
  7.       /authorize=anon  
  8.       /accessToken=anon  
  9.       /userInfo=anon  
  10.   
  11.       /** = user  
  12.     </value>  
  13. </property>   

對(duì)于oauth2的幾個(gè)地址/authorize/accessToken、/userInfo都是匿名可訪問的。

 

其他源碼請(qǐng)直接下載文檔查看。

 

服務(wù)器維護(hù)

訪問localhost:8080/chapter17-server/,登錄后進(jìn)行客戶端管理和用戶管理。

客戶端管理就是進(jìn)行客戶端的注冊(cè),如新浪微博的第三方應(yīng)用就需要到新浪微博開發(fā)平臺(tái)進(jìn)行注冊(cè);用戶管理就是進(jìn)行如新浪微博用戶的管理。

 

對(duì)于授權(quán)服務(wù)和資源服務(wù)的實(shí)現(xiàn)可以參考新浪微博開發(fā)平臺(tái)的實(shí)現(xiàn):

http://open.weibo.com/wiki/授權(quán)機(jī)制說明 

http://open.weibo.com/wiki/微博API 

 

客戶端

客戶端流程:如果需要登錄首先跳到oauth2服務(wù)端進(jìn)行登錄授權(quán),成功后服務(wù)端返回auth code,然后客戶端使用auth code去服務(wù)器端換取access token,最好根據(jù)access token獲取用戶信息進(jìn)行客戶端的登錄綁定。這個(gè)可以參照如很多網(wǎng)站的新浪微博登錄功能,或其他的第三方賬號(hào)登錄功能。

POM依賴

此處我們使用apache oltu oauth2客戶端實(shí)現(xiàn)。     

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>   

其他的請(qǐng)參考pom.xml。

 

OAuth2Token

類似于UsernamePasswordToken和CasToken;用于存儲(chǔ)oauth2服務(wù)端返回的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客戶端的身份驗(yàn)證控制;如果當(dāng)前用戶還沒有身份驗(yàn)證,首先會(huì)判斷url中是否有code(服務(wù)端返回的auth code),如果沒有則重定向到服務(wù)端進(jìn)行登錄并授權(quán),然后返回auth code;接著OAuth2AuthenticationFilter會(huì)用auth code創(chuàng)建OAuth2Token,然后提交給Subject.login進(jìn)行登錄;接著OAuth2Realm會(huì)根據(jù)OAuth2Token進(jìn)行相應(yīng)的登錄邏輯。  

Java代碼  
  1. public class OAuth2AuthenticationFilter extends AuthenticatingFilter {  
  2.     //oauth2 authc code參數(shù)名  
  3.     private String authcCodeParam = "code";  
  4.     //客戶端id  
  5.     private String clientId;  
  6.     //服務(wù)器端登錄成功/失敗后重定向到的客戶端地址  
  7.     private String redirectUrl;  
  8.     //oauth2服務(wù)器響應(yīng)類型  
  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)) {//如果服務(wù)端返回了錯(cuò)誤  
  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.                 //如果用戶沒有身份驗(yàn)證,且沒有auth code,則重定向到服務(wù)端授權(quán)  
  31.                 saveRequestAndRedirectToLogin(request, response);  
  32.                 return false;  
  33.             }  
  34.         }  
  35.         //執(zhí)行父類里的登錄邏輯,調(diào)用Subject.login登錄  
  36.         return executeLogin(request, response);  
  37.     }  
  38.   
  39.     //登錄成功后的回調(diào)方法 重定向到成功頁面  
  40.     protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,  ServletResponse response) throws Exception {  
  41.         issueSuccessRedirect(request, response);  
  42.         return false;  
  43.     }  
  44.   
  45.     //登錄失敗后的回調(diào)   
  46.     protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException ae, ServletRequest request,  
  47.                                      ServletResponse response) {  
  48.         Subject subject = getSubject(request, response);  
  49.         if (subject.isAuthenticated() || subject.isRemembered()) {  
  50.             try { //如果身份驗(yàn)證成功了 則也重定向到成功頁面  
  51.                 issueSuccessRedirect(request, response);  
  52.             } catch (Exception e) {  
  53.                 e.printStackTrace();  
  54.             }  
  55.         } else {  
  56.             try { //登錄失敗時(shí)重定向到失敗頁面  
  57.                 WebUtils.issueRedirect(request, response, failureUrl);  
  58.             } catch (IOException e) {  
  59.                 e.printStackTrace();  
  60.             }  
  61.         }  
  62.         return false;  
  63.     }  
  64. }   

該攔截器的作用:

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

2、接著如果用戶還沒有身份驗(yàn)證,判斷是否有auth code參數(shù)(即是不是服務(wù)端授權(quán)之后返回的),如果沒有則重定向到服務(wù)端進(jìn)行授權(quán);

3、否則調(diào)用executeLogin進(jìn)行登錄,通過auth code創(chuàng)建OAuth2Token提交給Subject進(jìn)行登錄;

4、登錄成功將回調(diào)onLoginSuccess方法重定向到成功頁面;

5、登錄失敗則回調(diào)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  
  38.             OAuthClientRequest userInfoRequest =   
  39.                 new OAuthBearerClientRequest(userInfoUrl)  
  40.                     .setAccessToken(accessToken).buildQueryMessage();  
  41.             OAuthResourceResponse resourceResponse = oAuthClient.resource(  
  42.                 userInfoRequest, OAuth.HttpMethod.GET, OAuthResourceResponse.class);  
  43.             String username = resourceResponse.getBody();  
  44.             return username;  
  45.         } catch (Exception e) {  
  46.             throw new OAuth2AuthenticationException(e);  
  47.         }  
  48.     }  
  49. }  

Realm首先只支持OAuth2Token類型的Token;然后通過傳入的auth code去換取access token;再根據(jù)access token去獲取用戶信息(用戶名),然后根據(jù)此信息創(chuàng)建AuthenticationInfo;如果需要AuthorizationInfo信息,可以根據(jù)此處獲取的用戶名再根據(jù)自己的業(yè)務(wù)規(guī)則去獲取。

 

Spring shiro配置(spring-config-shiro.xml)  

Java代碼  
  1. <bean id="oAuth2Realm"   
  2.     class="com.github.zhangkaitao.shiro.chapter18.oauth2.OAuth2Realm">  
  3.   <property name="cachingEnabled" value="true"/>  
  4.   <property name="authenticationCachingEnabled" value="true"/>  
  5.   <property name="authenticationCacheName" value="authenticationCache"/>  
  6.   <property name="authorizationCachingEnabled" value="true"/>  
  7.   <property name="authorizationCacheName" value="authorizationCache"/>  
  8.   <property name="clientId" value="c1ebe466-1cdc-4bd3-ab69-77c3561b9dee"/>  
  9.   <property name="clientSecret" value="d8346ea2-6017-43ed-ad68-19c0f971738b"/>  
  10.   <property name="accessTokenUrl"   
  11.      value="http://localhost:8080/chapter17-server/accessToken"/>  
  12.   <property name="userInfoUrl" value="http://localhost:8080/chapter17-server/userInfo"/>  
  13.   <property name="redirectUrl" value="http://localhost:9080/chapter17-client/oauth2-login"/>  
  14. </bean>   

OAuth2Realm需要配置在服務(wù)端申請(qǐng)的clientIdclientSecret;及用于根據(jù)auth code換取access tokenaccessTokenUrl地址;及用于根據(jù)access token換取用戶信息(受保護(hù)資源)的userInfoUrl地址。 

 

Java代碼  
  1. <bean id="oAuth2AuthenticationFilter"   
  2.     class="com.github.zhangkaitao.shiro.chapter18.oauth2.OAuth2AuthenticationFilter">  
  3.   <property name="authcCodeParam" value="code"/>  
  4.   <property name="failureUrl" value="/oauth2Failure.jsp"/>  
  5. </bean>   

此OAuth2AuthenticationFilter用于攔截服務(wù)端重定向回來的auth code。  

 

Java代碼  
  1. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">  
  2.   <property name="securityManager" ref="securityManager"/>  
  3.   <property name="loginUrl" value="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"/>  
  4.   <property name="successUrl" value="/"/>  
  5.   <property name="filters">  
  6.       <util:map>  
  7.          <entry key="oauth2Authc" value-ref="oAuth2AuthenticationFilter"/>  
  8.       </util:map>  
  9.   </property>  
  10.   <property name="filterChainDefinitions">  
  11.       <value>  
  12.           / = anon  
  13.           /oauth2Failure.jsp = anon  
  14.           /oauth2-login = oauth2Authc  
  15.           /logout = logout  
  16.           /** = user  
  17.       </value>  
  18.   </property>  
  19. </bean>  

此處設(shè)置loginUrlhttp://localhost:8080/chapter17-server/authorize

client_id=c1ebe466-1cdc-4bd3-ab69-77c3561b9dee&response_type=code&redirect_uri=http://localhost:9080/chapter17-client/oauth2-login";其會(huì)自動(dòng)設(shè)置到所有的AccessControlFilter,如oAuth2AuthenticationFilter;另外/oauth2-login = oauth2Authc表示/oauth2-login地址使用oauth2Authc攔截器攔截并進(jìn)行oauth2客戶端授權(quán)。

 

測試

1、首先訪問http://localhost:9080/chapter17-client/,然后點(diǎn)擊登錄按鈕進(jìn)行登錄,會(huì)跳到如下頁面: 


 

2、輸入用戶名進(jìn)行登錄并授權(quán);

3、如果登錄成功,服務(wù)端會(huì)重定向到客戶端,即之前客戶端提供的地址http://localhost:9080/chapter17-client/oauth2-login?code=473d56015bcf576f2ca03eac1a5bcc11,并帶著auth code過去;

4、客戶端的OAuth2AuthenticationFilter會(huì)收集此auth code,并創(chuàng)建OAuth2Token提交給Subject進(jìn)行客戶端登錄;

5、客戶端的Subject會(huì)委托給OAuth2Realm進(jìn)行身份驗(yàn)證;此時(shí)OAuth2Realm會(huì)根據(jù)auth code換取access token,再根據(jù)access token獲取受保護(hù)的用戶信息;然后進(jìn)行客戶端登錄。

 

到此OAuth2的集成就完成了,此處的服務(wù)端和客戶端相對(duì)比較簡單,沒有進(jìn)行一些異常檢測,請(qǐng)參考如新浪微博進(jìn)行相應(yīng)API及異常錯(cuò)誤碼的設(shè)計(jì)。   

    

 

 

  

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
認(rèn)證鑒權(quán)與API權(quán)限控制在微服務(wù)架構(gòu)中的設(shè)計(jì)與實(shí)現(xiàn):授權(quán)碼模式 | Aoho''s Blog
微服務(wù)系統(tǒng)之認(rèn)證管理詳解
Django中使用第三方登錄
OAuth授權(quán)的Java實(shí)現(xiàn)詳解
認(rèn)證鑒權(quán)與API權(quán)限控制在微服務(wù)架構(gòu)中的設(shè)計(jì)與實(shí)現(xiàn)(二)
為PHPBB加一個(gè)oauth認(rèn)證方式統(tǒng)一登陸SSO
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服