微软OA认证/ADFS认证 java 源码解析

  • Post author:
  • Post category:java


依照项目的需要,客户需要使用微软的认证,仔细走了一遍官方的源码,希望可以给后来人 指条路

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代码解析,希望可以帮助后人少走弯路



版权声明:本文为a897180673原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。