OAuth2 Java Shiro 客户端

OAuth2 Java Shiro 客户端

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

客户端

客户端流程:如果需要登录首先跳到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.     //登录成功后的回调方法 重定向到成功页面  
  40.     protected boolean onLoginSuccess(AuthenticationToken token, Subject subject, ServletRequest request,  ServletResponse response) throws Exception {  
  41.         issueSuccessRedirect(request, response);  
  42.         return false;  
  43.     }  
  44.   
  45.     //登录失败后的回调   
  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 { //如果身份验证成功了 则也重定向到成功页面  
  51.                 issueSuccessRedirect(request, response);  
  52.             } catch (Exception e) {  
  53.                 e.printStackTrace();  
  54.             }  
  55.         } else {  
  56.             try { //登录失败时重定向到失败页面  
  57.                 WebUtils.issueRedirect(request, response, failureUrl);  
  58.             } catch (IOException e) {  
  59.                 e.printStackTrace();  
  60.             }  
  61.         }  
  62.         return false;  
  63.     }  
  64. }   

该拦截器的作用:

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  
  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;再根据access token去获取用户信息(用户名),然后根据此信息创建AuthenticationInfo;如果需要AuthorizationInfo信息,可以根据此处获取的用户名再根据自己的业务规则去获取。

 

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需要配置在服务端申请的clientId和clientSecret;及用于根据auth code换取access token的accessTokenUrl地址;及用于根据access token换取用户信息(受保护资源)的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用于拦截服务端重定向回来的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&amp;response_type=code&amp;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>  

此处设置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/,然后点击登录按钮进行登录,会跳到如下页面: 


 

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及异常错误码的设计。   

    

 

 

示例源代码:https://github.com/zhangkaitao/shiro-example


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值