oauth2 学习(一)-使用Apache oltu实现oauth2的授权服务器
最近做oauth2预研,查了相当多的资料
因为现有的项目是使用java 语言来实现的,且不打算直接去实现这一整套的标准。因此先去官网(https://oauth.net/code/)看了下现有的java版实现。其实还有其他的实现没有收录进去。
比较之后发现资料相对较多的是Apache oltu以及 spring sercurity oauth.因为都是开源的,就去把源代码都clone下来了。个人认为Oltu相对来说更轻量,也更简单,是对oauth2的简单实现。很多后续校验的事情都需要我们自己去做,但这也是它灵活的一面。所以一开始,是决定使用Apache 的oltu。参考了杨开涛的博客(OAuth2集成——《跟我学Shiro》)使用oltu实现了一个简单的认证服务器。
一开始是打算写三个服务的oauthservice,oauthclient,oauthresource,后来为了省时间直接把客户端也集成到服务里面了,交互界面也只是几个简单的输入框。
认证服务主要有几个接口
/login:登录接口
/Oauth/authorize:获取授权码的接口
/Oauth/getCode:这个其实就是授权码接口,只是例子中后端没有存储登录状态,做了个中转
/Oauth/ accesstoken:获取访问令牌
下面来说说每个接口具体做了什么事
- 获取授权码
- 将请求转换成oltu的认证请求OauthAuthzRequest
- 从 OauthAuthzRequest 读取客户端信息(clientId,redirectUrl,response_type)
- 校验客户端信息
- 校验成功后生成访问令牌
- 存储访问令牌
- 使用oltu的OAuthASResponse构建oauth响应
- 在响应中设置好授权码,state等信息
- 重定向到客户端的redirectUrl
public Object getCode(HttpServletRequest request) { try { OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(request); OAuthResponse oAuthResponse; String clientId=oauthRequest.getClientId(); //校验client信息 if(!oauthClientService.checkClient(clientId)) { return ControllerHelper .getResponseEntity(HttpServletResponse.SC_BAD_REQUEST , OAuthError.TokenResponse.INVALID_CLIENT , ErrorConstants.ERROR_CLIENT_MSG); } //获取登陆信息 //已经登录校验内部token信息,没有登陆,校验登陆信息 String token=request.getParameter("token"); if(StringUtils.isEmpty(token))//token不存在及用户没有登陆,非法访问 { return ControllerHelper .getResponseEntity(HttpServletResponse.SC_BAD_REQUEST , OAuthError.CodeResponse.ACCESS_DENIED , ErrorConstants.ERROR_CLIENT_LOGIN); } else {//校验token 服务器端对应的token是否存在,及获取用户信息等 // checktoken() } //生成授权码 String authcode=null; String responseType=oauthRequest.getParam(OAuth.OAUTH_RESPONSE_TYPE); if(responseType.equals(ResponseType.CODE.toString())) { OAuthIssuerImpl oAuthIssuerImpl=new OAuthIssuerImpl(new MD5Generator()); authcode=oAuthIssuerImpl.authorizationCode(); //保存授权码 oauthClientService.saveCode(clientId, authcode); } //Oauth 响应 OAuthASResponse.OAuthAuthorizationResponseBuilder builder= OAuthASResponse.authorizationResponse(request, HttpServletResponse.SC_FOUND); //设置授权码 builder.setCode(authcode); String redirectURI=oauthRequest.getParam(OAuth.OAUTH_REDIRECT_URI); oAuthResponse=builder.location(redirectURI).buildQueryMessage(); //根据OAuthResponse返回ResponseEntity响应 HttpHeaders headers = new HttpHeaders(); headers.setLocation(new URI(oAuthResponse.getLocationUri())); return new ResponseEntity(headers, HttpStatus.valueOf(oAuthResponse.getResponseStatus())); } catch (Exception e) { logger.error(e.getCause().getMessage(),e); } return null; }
- 获取访问令牌
- 将请求转换成oltu的token 获取请求OAuthTokenRequest
- 从OauthTokenRequest读取客户端信息
- 校验客户端信息
- 生成访问令牌token
- 存储访问令牌
- 构建oauth2响应oAuthResponse
- 返回到客户端
public Object getToken(HttpServletRequest request) throws OAuthSystemException { try { OAuthTokenRequest oAuthTokenRequest= new OAuthTokenRequest(request); String clientId=oAuthTokenRequest.getClientId(); String clientKey= oAuthTokenRequest.getClientSecret(); if(!oauthClientService.checkClient(clientId,clientKey)) { return ControllerHelper.getResponseEntity(HttpServletResponse.SC_BAD_REQUEST, OAuthError.TokenResponse.INVALID_CLIENT, ErrorConstants.ERROR_CLIENT_MSG); } OAuthResponse oAuthResponse; String authcode= oAuthTokenRequest.getCode(); String grantType= oAuthTokenRequest.getGrantType(); if(GrantType.AUTHORIZATION_CODE.toString().equals(grantType) && authcode.equals(oauthClientService.getCode(clientId))) { //生成token OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator()); final String accessToken = oauthIssuerImpl.accessToken(); oauthClientService.saveAccessToken(accessToken, ""); //生成OAuth响应 oAuthResponse = OAuthASResponse .tokenResponse(HttpServletResponse.SC_OK) .setAccessToken(accessToken) .setExpiresIn(String.valueOf( 3600L)) .buildJSONMessage(); //根据OAuthResponse生成ResponseEntity return new ResponseEntity(oAuthResponse.getBody(), HttpStatus.valueOf(oAuthResponse.getResponseStatus())); } else{ return ControllerHelper.getResponseEntity(HttpServletResponse.SC_BAD_REQUEST, OAuthError.TokenResponse.INVALID_GRANT, ErrorConstants.ERROR_AUTH_CODE); } } catch (Exception e) { logger.error(e.getMessage(),e); return ControllerHelper.getResponseEntity(HttpServletResponse.SC_BAD_REQUEST, ErrorConstants.ERROR_UNKNOW, e.getCause().getMessage()); } }
客户端主要有一下两个接口
/requestAuth: 重定向到拼接好的授权请求url
@RequestMapping("/requestAuth") public ModelAndView requestAuth(@ModelAttribute("oauthParams") OauthParam oauthParams) { try { OAuthClientRequest request = OAuthClientRequest .authorizationLocation(oauthParams.getAuthzEndpoint()) .setClientId(oauthParams.getClientId()) .setRedirectURI(oauthParams.getRedirectUri()) .setResponseType(ResponseType.CODE.toString()) .setScope(oauthParams.getScope()) .setState(oauthParams.getState()) .buildQueryMessage(); return new ModelAndView(new RedirectView(request.getLocationUri())); } catch (Exception e) { logger.error(e.getMessage(),e); return null; } }
/redirect:获取授权码后,处理授权码的重定向地址。
OAuthAuthzResponse oar = null; oar = OAuthAuthzResponse.oauthCodeAuthzResponse(request); String code = oar.getCode();//获取授权码 OAuthClientRequest request2 =OAuthClientRequest .tokenLocation(oauthParams.getTokenEndpoint()) .setClientId(oauthParams.getClientId()) .setClientSecret(oauthParams.getClientSecret()) .setRedirectURI(oauthParams.getRedirectUri()) .setCode(code) .setGrantType(GrantType.AUTHORIZATION_CODE) .buildBodyMessage(); OAuthClient client=new OAuthClient(new URLConnectionClient()); Class<? extends OAuthAccessTokenResponse> cl = OAuthJSONAccessTokenResponse.class; //请求token OAuthAccessTokenResponse oauthResponse=client.accessToken(request2,cl); String token=oauthResponse.getAccessToken();//获取token
源码地址:https://github.com/huanglin101/springboot_oltu_oauth2.git