一、关于OAuth2.0
1.什么是OAuth2.0
OAuth2.0(开放授权)是一个关于授权的开放的网络协议。
2.OAuth2.0的作用是什么?
让客户端安全可控地获取用户的授权,与服务提供商之间进行交互。可以免去用户同步的麻烦,同时也增加了用户信息的安全。
3.设计理念
OAuth在第三方应用与服务提供商之间设置了一个授权层。第三方应用不能直接登录服务提供商,只能登录授权层,以此将用户与客户端区分开来。第三方应用登录授权层所用的令牌,与用户的密码不同。用户可以在登录授权的时候,指定授权层令牌的权限范围和有效期。
第三方应用登录授权层以后,服务提供商根据令牌的权限范围和有效期,向第三方应用开放用户资源。
4.应用场景
实现第三方应用的接口访问控制。第三方应用登录。
比如:
5.运行流程
(A)用户打开客户端以后,客户端要求用户给予授权。
(B)用户同意给予客户端授权。
(C)客户端使用上一步获得的授权,向认证服务器申请令牌。
(D)认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
(E)客户端使用令牌,向资源服务器申请获取资源。
(F)资源服务器确认令牌无误,同意向客户端开放资源。
6.客户端的授权模式
客户端必须得到用户的授权(authorization grant),才能获得令牌(access token)。OAuth 2.0定义了四种授权方式。
- 授权码模式(authorization code)
- 简化模式(implicit)
- 密码模式(resource owner password credentials)
- 客户端模式(client credentials)
二、授权码模式
授权码模式(authorization code)是功能最完整、流程最严密的授权模式。它的特点就是通过客户端的后台服务器,与"服务提供商"的认证服务器进行互动。
(A)用户访问客户端,后者将前者导向认证服务器。
(B)用户选择是否给予客户端授权。
(C)假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。
(D)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌。这一步是在客户端的后台的服务器上完成的,对用户不可见。
(E)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。
下面是上面这些步骤所需要的参数。
A步骤中,客户端申请认证的URI,包含以下参数:
- response_type:表示授权类型,必选项,此处的值固定为"code"
- client_id:表示客户端的ID,必选项
- redirect_uri:表示重定向URI,可选项
- scope:表示申请的权限范围,可选项
- state:表示客户端的当前状态,可以指定任意值,认证服务器会原封不动地返回这个值。
三、授权码模式代码(基于springboot)
应用案例运行流程
应用B的pom.xml需要添加oauth2的相关依赖:
<!--oauth2相关依赖 -->
<dependency>
<groupId>org.apache.oltu.oauth2</groupId>
<artifactId>org.apache.oltu.oauth2.authzserver</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.oltu.oauth2</groupId>
<artifactId>org.apache.oltu.oauth2.resourceserver</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.oltu.oauth2</groupId>
<artifactId>org.apache.oltu.oauth2.common</artifactId>
<version>1.0.0</version>
</dependency>
应用A的配置文件application.properties:
#应用id和秘钥 用于注册表示用户 获取token值会用到
client.ak = c3c31e1e-2117-4eee-8ca6-011594675ef1
client.sk = 5143faab-67e6-48aa-acf2-8e716b4b5983
client.oauth.server.host = hostname #服务B的ip地址
client.oauth.server.token.url = http://${client.oauth.server.host}/accessToken
client.oauth.server.redirect.url = http://127.0.0.1:8080
3.1 浏览器获取应用B端的code值
1.前端js发起请求
MC.http.ajax({
async: false,
url: clientUrl + "/remote/siti/test?callback=?",
data: "",
timeout: 500,
dataType: 'jsonp',
type: 'get',
success: function (rsp) {
if (null != rsp && rsp.result == "ok") {
isTestConnect = true;
MC.cookie.set("isConnection", "1");
var code = null;
var busType = null;
MC.http.ajax({
url: '/teacher/user/code',
dataType: 'json',
type: 'get',
async: false,
success: function (rsp) {
// alert(rsp);
code = rsp.code;
},
error: function () {
MC.msg('alert', '获取code失败,请稍后再试');
}
})
var urls = url.split("/");
busType = urls[urls.length - 1];
window.location.href = cIp + "/online?code=" + code + "&busType=" + busType;//cip=http://hostname:port 获取code成功后,刷新页面到指定的url
return;
}
},
error: function () {
MC.cookie.set("isConnection", "0");
}
})
2.后台代码
@Autowired
private OAuthService oAuthService;
/**
* controller
* 用户登录状态获得和用户关联的code
* @param request
* @return
*/
@RequestMapping(value = "/code", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public Map<String, Object> code( HttpServletRequest request){
String code = oAuthService.fetchOrGenCode(request);
HashMap codeMap = new HashMap();
codeMap.put("code",code);
return codeMap;
}
@Override
public String fetchOrGenCode(HttpServletRequest request) {
//获取缓存中的用户信息
String userNo = (String) request.getSession().getAttribute("UserNo");
if(null == userNo){
userNo = (String) request.getSession().getAttribute("outUserNo");
}
return code(userNo);
}
public String code(String userNo){
try {
String code = getCode(userNo);
if (null != code) {
return code;
}else{
if (null != userNo) {
String authorizationCode = GeneAuthorizationCode(userNo);
saveCode(userNo, authorizationCode);
addAuthCode(authorizationCode,userNo);
return authorizationCode;
}
}
} catch (Exception e) {
if (null != userNo) {
String authorizationCode = GeneAuthorizationCode(userNo);
saveCode(userNo, authorizationCode);
addAuthCode(authorizationCode,userNo);
return authorizationCode;
}
}
return null;
}
2.应用A 通过code+ak+sk值去应用B获取token值
/**
* 过滤器 拦截/inner/* 路径下所有的请求
*/
@Configuration
@EnableAsync
@PropertySource("classpath:message.properties")
public class WebConfig {
@Bean
public FilterRegistrationBean innerCheckFilterRegistrationBean(InnerCheckFilter innerCheckFilter) {
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
filterRegistrationBean.setFilter(innerCheckFilter);
filterRegistrationBean.setEnabled(true);
filterRegistrationBean.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));
filterRegistrationBean.addUrlPatterns("/inner/*", "*.html", "*.htm");
filterRegistrationBean.setOrder(3);
return filterRegistrationBean;
}
}
/**
* 拦截器InnerCheckFilter
*/
@Component("innerCheckFilter")
public class InnerCheckFilter implements Filter {
private static Set<String> beforeLoginSkipUrls = new HashSet<String>();
@Override
public void init(FilterConfig filterConfig) throws ServletException {
log.info("ZxtkCheck filter init...");
beforeLoginSkipUrls.add("/inner/login");
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
HttpSession session = request.getSession();
request.setCharacterEncoding("UTF-8");
String[] tokenMessage = new String[0];
String code = request.getParameter("code");
String sessionToken = request.getParameter("token");
if (sessionToken == null){
if (code != null) {
//获取taken
OAuthAccessTokenResponse tokenResponse;
try {
tokenResponse = oauthClient.makeTokenRequestWithAuthCode(code); //通过code获取token值
if (tokenResponse != null) {//表示已经获取到了token 解析token中的值
tokenMessage = Base64Util.deCode(tokenResponse.getAccessToken().toString()).split("#");
session.setAttribute("token", tokenResponse.getAccessToken());
session.setAttribute("code", code);
session.setAttribute(code, tokenResponse.getAccessToken());
session.setMaxInactiveInterval(tokenResponse.getExpiresIn().intValue());
}else{
response.sendRedirect("/login.html");
}
} catch (Exception e) {
e.printStackTrace();
}
response.sendRedirect("/inner/login?usrId=" + tokenMessage[1]);
}else{
chain.doFilter(request,response);
}
}else{
chain.doFilter(request,response);
}
}
@Override
public void destroy() {
beforeLoginSkipUrls.clear();
log.info("InnerCheck filter destroy...");
}
}
/**
* 获取token的实现类
*/
@Component
public class OauthClient {
@Value("${client.ak}")
private String ak;
@Value("${client.sk}")
private String sk;
@Value("${client.oauth.server.token.url}")
private String OAUTH_SERVER_TOKEN_URL;
@Value("${client.oauth.server.redirect.url}")
private String OAUTH_SERVER_REDIRECT_URI;
/**
* 根据授权码获取accessToken
* @param authCode 服务器端code
* @return OAuthAccessTokenResponse
* @throws OAuthProblemException
* @throws OAuthSystemException
*/
public OAuthAccessTokenResponse makeTokenRequestWithAuthCode(String authCode)
throws OAuthProblemException, OAuthSystemException {
OAuthClientRequest request = OAuthClientRequest
.tokenLocation(OAUTH_SERVER_TOKEN_URL)//访问地址
.setClientId(ak)//ak
.setClientSecret(sk)//sk
.setGrantType(GrantType.AUTHORIZATION_CODE)
.setCode(authCode)
.setRedirectURI(OAUTH_SERVER_REDIRECT_URI)//返回地址
.buildBodyMessage();
OAuthClient oAuthClient = new OAuthClient(new URLConnectionClient());
return oAuthClient.accessToken(request);
}
}
token返回值: