依照项目的需要,客户需要使用微软的认证,仔细走了一遍官方的源码,希望可以给后来人 指条路
1.先去微软的官网下载java版本的源代码
上面的下载页面 会有一套具体的申请账号+配置流程 ,跟着做就可以了
把下载好的java工程应用,导入到Tomcat上面运行就ok.
这里说一下 具体的的代码流程
先来个总体的感受,上图 (图片太大,点击图片看大图,或者下载看)
这个就是大体的认证的过程
1.案例中,所有的http请求都要通过 过滤器
2.要完成一次认证,客户端必须要2次访问Tomcat
3.只要Tomcat内存中,有Session的key=principal,只要这个key=principal 对应的数值不过期(默认3600)
那么所有的请求中 就不会访问 微软的认证服务器
有了宏观的感受,下面看看具体的代码,其实微软官方的这个代码中,文件还是比较多的,但是更多的都是一些工具类,真正核心的就是2个文件 BasicFilter和AadController
一个是过滤器 过滤所有请求,一个是控制层处理token,这个控制层的地址一般就是回调地址
这两个文件中,最最最 重要的是过滤器,过滤器一共有300多行代码,太长了,其实核心的也就几十行,更多的是封装的独立的函数 ,这个可以不管,下面上这几十行 核心 代码
* Copyright © Microsoft Open Technologies, Inc.
*
* All Rights Reserved
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS
* OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION
* ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A
* PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT.
*
* See the Apache License, Version 2.0 for the specific language
* governing permissions and limitations under the License.
******************************************************************************/
package com.microsoft.aad.adal4jsample;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLEncoder;
import java.text.ParseException;
import java.util.*;
import java.util.concurrent.*;
import javax.naming.ServiceUnavailableException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import com.microsoft.aad.adal4j.AuthenticationContext;
import com.microsoft.aad.adal4j.AuthenticationException;
import com.microsoft.aad.adal4j.AuthenticationResult;
import com.microsoft.aad.adal4j.ClientCredential;
import com.nimbusds.jwt.JWTParser;
import com.nimbusds.oauth2.sdk.AuthorizationCode;
import com.nimbusds.openid.connect.sdk.AuthenticationErrorResponse;
import com.nimbusds.openid.connect.sdk.AuthenticationResponse;
import com.nimbusds.openid.connect.sdk.AuthenticationResponseParser;
import com.nimbusds.openid.connect.sdk.AuthenticationSuccessResponse;
import org.apache.commons.lang3.StringUtils;
public class BasicFilter implements Filter {
public static final String STATES = "states";
public static final String STATE = "state";
public static final Integer STATE_TTL = 3600;
public static final String FAILED_TO_VALIDATE_MESSAGE = "Failed to validate data received from Authorization service - ";
private String clientId = "";
private String clientSecret = "";
private String tenant = "";
private String authority;
public void destroy() {
}
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (request instanceof HttpServletRequest) {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
try {
String currentUri = httpRequest.getRequestURL().toString();
String queryStr = httpRequest.getQueryString();
String fullUrl = currentUri + (queryStr != null ? "?" + queryStr : "");
// 下面if()中函数用于判断Session中是否有key=principal,封装成了函数,其实函数里面就一行代码
if (!AuthHelper.isAuthenticated(httpRequest)) {
// 下面if()中函数用来判断是否包含Code 信息
if (AuthHelper.containsAuthenticationData(httpRequest)) {
//如果包含了,那么就要进行处理code-->token,并把token存到Session,key=principal
processAuthenticationData(httpRequest, currentUri, fullUrl);
} else {
//如果不包含,那就跳转到微软的认证界面
sendAuthRedirect(httpRequest, httpResponse);
return;
}
}
// 下面if()中函数用来判断 Session中key=principal的token是否过期
if (isAuthDataExpired(httpRequest)) {
updateAuthDataUsingRefreshToken(httpRequest);
}
} catch (AuthenticationException authException) {
//如果出错, 移除Session,并跳转微软认证地址
removePrincipalFromSession(httpRequest);
sendAuthRedirect(httpRequest, httpResponse);
return;
} catch (Throwable exc) {
httpResponse.setStatus(500);
request.setAttribute("error", exc.getMessage());
request.getRequestDispatcher("/error.jsp").forward(request, response);
}
}
chain.doFilter(request, response);
}
过滤的核心就是上面的几行代码,只不过某些小功能也封装成函数了,函数嵌套调用比较多,看了头晕
下面就是
AadController
AadController 中的访问的接口一般做为回调函数,里面主要是拿Token进行相应的处理的,官方的案例是根据accessToken去申请用户信息然后返回给前台的界面
代码不是很长 就2个函数
package com.microsoft.aad.adal4jsample;
import java.net.HttpURLConnection;
import java.net.URL;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.json.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.microsoft.aad.adal4j.AuthenticationResult;
@Controller
@RequestMapping(value="/secure/aad" )
public class AadController {
@RequestMapping( method = { RequestMethod.GET, RequestMethod.POST })
public String getDirectoryObjects(ModelMap model, HttpServletRequest httpRequest,HttpServletResponse resp) {
HttpSession session = httpRequest.getSession();
//从session中取出Token
AuthenticationResult result = (AuthenticationResult) session.getAttribute(AuthHelper.PRINCIPAL_SESSION_NAME);
resp.setCharacterEncoding("utf-8");
if (result == null) {
model.addAttribute("error", new Exception("AuthenticationResult not found in session."));
return "error";
} else {
String data;
try {
String tenant = session.getServletContext().getInitParameter("tenant");
data = getUsernamesFromGraph(result, tenant);
//把用户信息放到model中
model.addAttribute("tenant", tenant);
model.addAttribute("user", data);
model.addAttribute("userInfo", result.getUserInfo());
} catch (Exception e) {
model.addAttribute("error", e);
return "error";
}
}
//跳转到前台界面进行展示
return "secure/aad";
}
//这个函数就是一个http请求,根据accessToken获取用户的信息.一共两个参数,一个是包含accessToken的认证信
//息,一个是租户,在申请微软的控制面板中有这个值
private String getUsernamesFromGraph( AuthenticationResult result, String tenant) throws Exception {
String urlString="https://graph.chinacloudapi.cn/me?api-version=2013-04-05";
URL url = new URL(urlString);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
// Set the appropriate header fields in the request header.
conn.setRequestProperty("api-version", "2013-04-05");
conn.setRequestProperty("Authorization", result.getAccessToken());
conn.setRequestProperty("Accept", "application/json;odata=minimalmetadata");
String goodRespStr = HttpClientHelper.getResponseStringFromConn(conn, true);
// logger.info("goodRespStr ->" + goodRespStr);
int responseCode = conn.getResponseCode();
JSONObject response = HttpClientHelper.processGoodRespStr(responseCode, goodRespStr);
JSONObject jsonUser;
jsonUser=response.getJSONObject("responseMsg");
StringBuilder builder = new StringBuilder();
User user = new User();
JSONHelper.convertJSONObjectToDirectoryObject(jsonUser, user);
builder.append(user.getUserPrincipalName() + "<br/>");
return builder.toString();
}
}
然后在界面中 用户就可以看到一些用户信息了,这些就是大体的 微软认证的 官方的 后台java代码解析,希望可以帮助后人少走弯路