第三章 MSAL API和Microsoft API的使用

本章围绕 Microsoft Authentication Library 的使用讲述如何调用 Microsoft API 。目录小节如下:

1.1 MSAL库常用API的介绍

1.2 scope的配置

1.3 GRAPH库的使用

1.4 手动请求Microsoft API

其他相关文章:

基于Microsoft的OAUTH2.0认证(一):基本概念梳理

基于Microsoft的OAUTH2.0认证(二):MSAL库的引用和定制

基于Microsoft的OAUTH2.0认证(四):常见错误汇总

1.1 MSAL库常用API的介绍

Oauth2.0 的认证从创建账户管理对象开始,认证管理类 PublicClientApplication 提供了两个静态方法createSingleAccountPublicClientApplication和createMultipleAccountPublicClientApplication分别对应单账户管理类和多账户管理类的创建。两个方法都有三个入参:分别是:

@NonNull final Context context, 一个指定的Context对象,可以是某个actiivty,也可以是ApplicationContext。

final int configFileResourceId, 在AzureAD中注册的应用身份验证数据,就是文章(二)中提到的xml文件。

@NonNull final ISingleAccountApplicationCreatedListener/ IMultipleAccountApplicationCreatedListener listener

创建成功的回调监听器,回调监听器有两个方法onCreated和onError,其中onCreated提供实例化成功的接口对象。

这两个方法的是构建和Microsoft认证平台链接的关键,Microsoft认证平台会检测传入的验证数据是否存在与AzureAD中,检测是否已经注册了BrowserTabActivity,两者有一方不匹配就会触发onError。如果检测无误,你就可以在 onCreated中 拿到实例化成功的ISingleAccountPublicClientApplication/IMultipleAccountPublicClientApplication接口对象(真正实现了这两个接口的实体类在MSAL库中,对外提供的只有接口对象) 。后续的获取token和刷新token都需要此处返回的实例化对象,如果你的认证操作不是一次性的,建议用全局变量将此处的对象予以保存。

单账户接口 ISingleAccountPublicClientApplication 顾名思义只能保持一个账户处于登录状态(signin),所以它提供了signIn()和signOut()方法,常用的API有如下几个:

signIn

入参:

@NonNull final Activity activity,
@Nullable final String loginHint,
@NonNull final String[] scopes,
@NonNull final AuthenticationCallback callback

异步登入账户,这个方法必须和signOut配合使用。如果重复调用会收到异常提示。从入参中看,我们不需要传入特定的账户。账户登录操作是在AuthorizationActivity中完成的,账户登录后将被记录到缓存中。每次调用signIn实际上都会先去检测缓存中是否存在登录的账号,只要有就会报错。所以成功调用signIn的前提是确保当前无登录中的账号。

signOut

入参:

@NonNull final SignOutCallback callback

异步登出账户,当然还有同步方法,是一个无入参且返回值为boolean类型的signOut()。这里的登出是真正的退出登录状态。

acquireTokenSilentAsync

入参:

@NonNull final String[] scopes,
@NonNull final String authority,
@NonNull final SilentAuthenticationCallback callback

异步刷新token,原则上不会弹出认证页面。MSAL库内部会判断是否是否需要刷新token:

①如果持有的refresn_token时效,才会弹出认证页面让用户重新登录。

②如果持有的refresh_token有效,且在token有效期内,直接返回当前token。

③如果持有的refresh_token有效,超过了token有效期,直接返回新的token。

acquireTokenSilent

入参:

@NonNull final Activity activity,
@NonNull final String[] scopes,
@NonNull final AuthenticationCallback callback

同步刷新token,具体操作同上。当然还有一个返回类型是IAuthenticationResult的acquireTokenSilent(),

多账户接口IMultipleAccountPublicClientApplication内部维系着一个账户队列(IAccount队列),没有signIn和signOut方法,只需要在调用acquireTokenXXX相关方法时传入指定的账号 IAccount 即可。形式上和 ISingleAccountPublicClientApplication 最大的区别就是账号的指定。常用的API有如下几个:

getAccounts()

获取当前缓存的 账户 队列,返回类型为List<IAccount>

acquireTokenSilentAsync

入参:

@NonNull final String[] scopes,
@NonNull final IAccount account,
@NonNull final String authority,
@NonNull final SilentAuthenticationCallback callback

异步刷新token,原则上不会弹出认证页面。MSAL库内部会判断是否是否需要刷新token:

①如果持有的refresn_token时效,才会弹出认证页面让用户重新登录。

②如果持有的refresh_token有效,且在token有效期内,直接返回当前token。

③如果持有的refresh_token有效,超过了token有效期,直接返回新的token。

acquireTokenSilent

入参:

@NonNull String[] scopes,

@NonNull IAccount iAccount,

@NonNull String authority

同步刷新token,具体操作同上。当然还有一个返回类型是IAuthenticationResult的acquireTokenSilent()。 但是在实际使用时,我发现这个方法并不能实时地更新token, 可能在后续的版本中会做出调整。

removeAccount

入参:

@Nullable final IAccount account,
@NonNull final RemoveAccountCallback callback

删除账户, 这里的删除操作不知道指的是删除什么,成功执行完删除操作后你会发现 getAccounts() 中已然能返回已删除的那个IAccount,可能在后续的版本中会做出调整。

究竟采用多账户还是单账户,完全取决于你的项目需求。至于access_token的更新完全不需要顾虑,MSAL库已经封装了针对token的刷新机制,就像我们在 onCreated 中拿不到refresh_token参数一样,MSAL库已经在缓存中保存下来,我们只需要去调用对应的xxxasync就行了。附上官方提供的单账户和多账户方案:

https://docs.microsoft.com/zh-cn/azure/active-directory/develop/single-multi-account

同时提供一份我写的OauthAuthHelper类,如下所示,其中针对账户token的存储,我用了开源的Reservoir,这里可以根据项目的实际场景自行调整。
package com.demo.sharemap;
import android.app.Activity;
import android.content.Context;
import android.text.TextUtils;
import android.widget.Toast;
import com.anupcowkur.reservoir.Reservoir;
import com.microsoft.identity.client.AuthenticationCallback;
import com.microsoft.identity.client.IAccount;
import com.microsoft.identity.client.IAuthenticationResult;
import com.microsoft.identity.client.IMultipleAccountPublicClientApplication;
import com.microsoft.identity.client.IPublicClientApplication;
import com.microsoft.identity.client.ISingleAccountPublicClientApplication;
import com.microsoft.identity.client.PublicClientApplication;
import com.microsoft.identity.client.SilentAuthenticationCallback;
import com.microsoft.identity.client.exception.MsalException;
import java.io.IOException;
import java.io.Serializable;
import java.util.List;
public class OauthHelper {
    public static final int TYPE_SINGLE = 0;//单账户模式
    public static final int TYPE_MULTIPLE = 1;//多账户模式
    private static int choiceType;
    private volatile static OauthHelper sInstance = null;
    private IAccount iAccount;
    private Context mContext;
    private static IMultipleAccountPublicClientApplication mMPCA = null;
    private static ISingleAccountPublicClientApplication mSPCA = null;
    private final String[] mScopes = {
             * 添加你需要的权限API
            "https://outlook.office.com/User.Read"
     * 最好在Application中实例化OauthHelper
     * Reservori是一个基于SharedPreference构建的开源库,用来存储账户和token
    private OauthHelper(Context ctx, int type, final IAuthenticationHelperCreatedListener listener) {
        choiceType = type;
        try {
            Reservoir.init(ctx, 1024 * 1024);
        } catch (IOException e) {
            e.printStackTrace();
        mContext = ctx;
        if (choiceType == TYPE_SINGLE) {
            PublicClientApplication.createSingleAccountPublicClientApplication(ctx, R.raw.auth_single_account,
                    new IPublicClientApplication.ISingleAccountApplicationCreatedListener() {
                        @Override
                        public void onCreated(ISingleAccountPublicClientApplication iSingleAccountPublicClientApplication) {
                            mSPCA = iSingleAccountPublicClientApplication;
                            if (null != listener) {
                                listener.onCreated(sInstance);
                        @Override
                        public void onError(MsalException e) {
                            if (null != listener) {
                                listener.onError(e);
        } else if (choiceType == TYPE_MULTIPLE) {
            PublicClientApplication.createMultipleAccountPublicClientApplication(ctx, R.raw.auth_multiple_account,
                    new IPublicClientApplication.IMultipleAccountApplicationCreatedListener() {
                        @Override
                        public void onCreated(IMultipleAccountPublicClientApplication iMultipleAccountPublicClientApplication) {
                            mMPCA = iMultipleAccountPublicClientApplication;
                            if (null != listener) {
                                listener.onCreated(sInstance);
                        @Override
                        public void onError(MsalException e) {
                            if (null != listener) {
                                listener.onError(e);
    public static void init(Context ctx,  int type, IAuthenticationHelperCreatedListener listener) {
        if (sInstance == null) {
            synchronized (OauthHelper.class) {
                if (sInstance == null) {
                    sInstance = new OauthHelper(ctx, type, listener);
                } else {
                    if(null != listener){
                        listener.onCreated(sInstance);
        } else {
            if(mMPCA == null){
                synchronized (OauthHelper.class) {
                    if(mMPCA == null){
                        sInstance = new OauthHelper(ctx, type, listener);
                    }else {
                        if(null != listener){
                            listener.onCreated(sInstance);
            }else {
                if(null != listener){
                    listener.onCreated(sInstance);
     * 获取当前实例对象,需要在init()方法执行后再调用
    public static OauthHelper getInstance() {
        return sInstance;
     * 单账户模式下登入
    public void signIn(Activity activity, String loginHint, AuthenticationCallback callback){
        if(null == mSPCA){
            return;
        mSPCA.signIn(activity, loginHint, mScopes, callback);
     * 单账户模式下登出
    public void signOut(ISingleAccountPublicClientApplication.SignOutCallback callback){
        if(null == mSPCA){
            return;
        mSPCA.signOut(callback);
     * 单账户模式下更新token,会显示用户认证页面
    public void requestTokenRefresh(Activity activity, final AuthenticationCallback callback){
        if(null == mSPCA){
            return;
        mSPCA.acquireToken(activity, mScopes, new AuthenticationCallback() {
            @Override
            public void onCancel() {
                callback.onCancel();
            @Override
            public void onSuccess(IAuthenticationResult iAuthenticationResult) {
                putToken(iAuthenticationResult.getAccount().getUsername(), iAuthenticationResult.getAccessToken());
                callback.onSuccess(iAuthenticationResult);
            @Override
            public void onError(MsalException e) {
                callback.onError(e);
     * 单账户模式下静默同步更新token
    public String requestTokenSync(){
        if(null == mSPCA){
            return "";
        String token = null;
        IAuthenticationResult iAuthenticationResult = null;
        IAccount iAccount = null;
        try {
            iAccount = mSPCA.getCurrentAccount().getCurrentAccount();
            iAuthenticationResult = mSPCA.acquireTokenSilent(mScopes, iAccount.getAuthority());
            if(null != iAuthenticationResult){
                token = iAuthenticationResult.getAccessToken();
                putToken(iAccount.getUsername(), iAuthenticationResult.getAccessToken());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (MsalException e) {
            e.printStackTrace();
        }finally {
            return token;
     * 单账户模式下静默异步更新token
    public void requestTokenAsync(final AuthenticationCallback callback){
        if(null == mSPCA){
            return;
        IAccount iAccount = null;
        try {
            iAccount = mSPCA.getCurrentAccount().getCurrentAccount();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (MsalException e) {
            e.printStackTrace();
        if(null != iAccount){
            mSPCA.acquireTokenSilentAsync(mScopes, iAccount.getAuthority(), new AuthenticationCallback() {
                @Override
                public void onCancel() {
                    callback.onCancel();
                @Override
                public void onSuccess(IAuthenticationResult iAuthenticationResult) {
                    putToken(iAuthenticationResult.getAccount().getUsername(), iAuthenticationResult.getAccessToken());
                    callback.onSuccess(iAuthenticationResult);
                @Override
                public void onError(MsalException e) {
                    callback.onError(e);
     * 多账户模式下初次请求token
    public void initOauthToken(final Activity activity, final AuthenticationCallback callback) {
        initOauthToken(activity, "", callback);
     * 多账户模式下初次请求token,指定账户
    public void initOauthToken(final Activity activity, String emailAddress, final AuthenticationCallback callback){
        if(null == mMPCA){
            return;
        AuthenticationCallback innerCallback = new AuthenticationCallback() {
            @Override
            public void onCancel() {
                iAccount = null;
                callback.onCancel();
            @Override
            public void onSuccess(IAuthenticationResult iAuthenticationResult) {
                iAccount = iAuthenticationResult.getAccount();
                Toast.makeText(activity, "认证成功", Toast.LENGTH_SHORT).show();
                callback.onSuccess(iAuthenticationResult);
            @Override
            public void onError(MsalException e) {
                iAccount = null;
                callback.onError(e);
        if (TextUtils.isEmpty(emailAddress)) {
            mMPCA.acquireToken(activity, mScopes, innerCallback);
        } else {
            mMPCA.acquireToken(activity, mScopes, emailAddress, innerCallback);
     * 多账户模式下静默同步更新token
    public String requestTokenSync(Context context, String emailAddress) {
        if(null == mMPCA){
            return "";
        String token = "";
        IAuthenticationResult iAuthenticationResult = null;
        try {
            List<IAccount> list = mMPCA.getAccounts();
            for (IAccount account : list) {
                if (account.getUsername().equals(emailAddress)) {
                    iAccount = account;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (MsalException e) {
            e.printStackTrace();
        if(null != iAccount){
            try {
                String authority = iAccount.getAuthority();
                iAuthenticationResult = mMPCA.acquireTokenSilent(mScopes, iAccount, authority);
                if(null == iAuthenticationResult){
                    return token;
                } else {
                    token = iAuthenticationResult.getAccessToken();
                    putToken(iAccount.getUsername(), iAuthenticationResult.getAccessToken());
            } catch (MsalException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                return token;
        return token;
     * 多账户模式下静默异步刷新token
     * @return whether the request is truly handled.
    public void requestTokenAsync(String emailAddress, final AuthenticationCallback callback){
        if (null == mMPCA) {
            return;
        IAccount iAccount = null;
        try {
            List<IAccount> list = mMPCA.getAccounts();
            for (IAccount account : list) {
                if (account.getUsername().equals(emailAddress)) {
                    iAccount = account;
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (MsalException e) {
            e.printStackTrace();
        if (iAccount != null) {
            mMPCA.acquireTokenSilentAsync(mScopes, iAccount, iAccount.getAuthority(), new SilentAuthenticationCallback() {
                @Override
                public void onSuccess(IAuthenticationResult iAuthenticationResult) {
                    putToken(iAuthenticationResult.getAccount().getUsername(), iAuthenticationResult.getAccessToken());
                    callback.onSuccess(iAuthenticationResult);
                @Override
                public void onError(MsalException e) {
                    callback.onError(e);
     * 多账户模式下删除账户
    public void removeAccount(final String emailAddress, final IMultipleAccountPublicClientApplication.RemoveAccountCallback callback){
        if(null == mMPCA){
           return;
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    List<IAccount> list = mMPCA.getAccounts();
                    for(IAccount account : list){
                        if(account.getUsername().equals(emailAddress)){
                            iAccount = account;
                    if(null != iAccount){
                        mMPCA.removeAccount(iAccount, callback);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (MsalException e) {
                    e.printStackTrace();
        }).start();
    public void removeNativeAccount(String accountAddress){
        try {
            Reservoir.delete(accountAddress);
        } catch (Exception e) {
    public void putToken(String accountAddress, String token){
        try {
            OauthEntry oauthEntry = Reservoir.get(accountAddress, OauthEntry.class);
            if(null != oauthEntry){
                if(!token.equals(oauthEntry.token)){
                    oauthEntry.token = token;
                    Reservoir.put(accountAddress, oauthEntry);
            } else {
                oauthEntry = new OauthEntry("", token);
                Reservoir.put(accountAddress, oauthEntry);
        catch (Exception e) {
    public void putCookie(String accountAddress, String cookie){
        try {
            OauthEntry oauthEntry = Reservoir.get(accountAddress, OauthEntry.class);
            if(null != oauthEntry){
                if(!cookie.equals(oauthEntry.cookie)){
                    oauthEntry.cookie = cookie;
                    Reservoir.put(accountAddress, oauthEntry);
            } else {
                oauthEntry = new OauthEntry(cookie, "");
                Reservoir.put(accountAddress, oauthEntry);
        catch (Exception e) {
    public String getToken(String accountAddress){
        String token = "";
        try {
            OauthEntry oauthEntry = Reservoir.get(accountAddress, OauthEntry.class);
            if(null != oauthEntry){
                token = oauthEntry.token;
        catch (Exception e) {
        finally {
            return token;
    public String getCookie(String accountAddress){
        String cookie = "";
        try {
            OauthEntry oauthEntry = Reservoir.get(accountAddress, OauthEntry.class);
            if(null != oauthEntry){
                cookie = oauthEntry.cookie;
        catch (Exception e) {
        finally {
            return cookie;
    public interface IAuthenticationHelperCreatedListener{
        public void onCreated(OauthHelper OauthHelper);
        public void onError(MsalException msalException);
        public void onCancel();
    // 用来存储token和cookie
    private class OauthEntry implements Serializable {
        String cookie;
        String token;
        protected OauthEntry(String cookie, String token) {
            this.cookie = cookie;
            this.token = token;
        @Override
        public String toString() {
            return  new StringBuilder().append(super.toString())
                    .append(" cookie:")
                    .append(cookie)
                    .append(" token: ")
                    .append(token).toString();

1.2 scope的配置

不管用哪个方法申请或更新access_token时,有一个必须传入的入参:scopes。scopes表示API权限组,是以String[]形式在构建账户管理对象时作为入参传入,它决定着用户在权限认证页面可以看到的待申请权限有哪些,也决定着你能否正常使用与待申请权限对应的API请求。

scope的配置分为两部分:1、AzureAD网站中的配置。2、代码中创建scope数组.

1.2.1 AzureAD网站中的配置

点击【API权限】你会发现,有个默认权限:User.Read。这个权限表示可以读取用户的个人信息。点击【添加权限】就可以看到权限集合入口,Microsoft将所有权限都统一到了Microsoft Graph中,当然你也可以选择旧版API权限。

不管是选择哪一类型的API权限,只有【添加权限】通过后,才会在已申请列表上看到配置的权限,有些权限存在添加失败的情况,比如EAS.ALL,这是因为GRAPH还未提供相关的数据支持,GRAPH本质上是一个过滤网关,调用GRAPH库就像是通过网关开辟的快捷通道,如果网关未提供快捷通道,你就无法通过GRAPH来获取相关的数据。添加权限成功的画面如下:

权限添加失败的情况下,会出现如下提示。为什么会有权限添加失败的情况,其实这不是你的配置有问题,问题出在GRAPH上,Microsoft现在主推Graph库,目的是规范API请求,但是有些权限在整合到GRAPH上时,没有提供对外的接口,导致你无法通过GRAPH去访问,所以这类权限就会出现添加失败。这里的失败只是意味着你不能用GRAPH提供的方法去访问调用。你还是有别的方法去申请这些失败的权限的,请看1.2.2。

1.2.2 代码中创建scope数组

一个普通的scope数组是如下所示的样子,Microsoft提供的MSAL库中,实际默认配置了三个权限:

openid :开放访问

offline_access :过期后允许再访问,和refresh_token的获取有关,如果不配置这个属性,就无法刷新token

profile :个人信息

这三个权限不需要你在AzureAD网站中配置,会默认在你传入的scope中追加上,但这不代表你就可以传入一个空的scope数组,构建PublicClientApplication的全部入参都是NoNull的。scopes数组的构建有两种模式,一种是直接以权限名的方式添加, 此时要注意,数组中的权限名必须全部是小写。如下所示:

private final String[] mScopes = {
       
"user.read"

这里使用小写的原因是

一种是以IDURL+权限名的方式组合,此时权限名需要和网站中大小写保持一致,如下所示:

private final String[] mScopes = {
       
"https://outlook.office365.com/User.Read"
};

IDURL指的是WEB API的接口地址,就像服务器地址一样,不同的IDURL指向不同的服务器网关,针对OAUTH2.0下的Microsoft平台,官方建议是使用https://graph.microsoft.com/ ,如果你采用上述权限名的添加方式,最终Microsoft认证平台看到是https://graph.microsoft.com/权限名。但实际上你配置成https://outlook.office365.com/https://outlook.office.com/等都可以正常使用。

注意:不同IDURL对不同的登录账号是有区别的,比如如果登录账号是一个outlook.com或hotmail.com级别的普通账号,只能识别https://outlook.office.com/User.Read,而一个企业级账号即可以识别https://outlook.office365.com/User.Read,也能识别https://outlook.office.com/User.Read

在1.2.1中提到,存在一些权限引用失败的情况,这种情况就可以使用IDURL+权限名的方式进行规避,但是User.Read必须配置上,因为graph也有默认的user.read权限,如果不配置,会认为当前使用的权限是graph类型的,从而导致你申请的其他权限是非法权限。

如果你不清楚还有哪些权限无法引用,可以直接配置成.default的形式,如下所示:

https://outlook.office.com/.default

设置成 .default后,默认会直接请求全部的可执行权限。 但原则上不建议这么设置,一个是多余的API权限你用不到,一个是用户在认证许可页面看到过多的权限申请,也不一定会同意。只申请自己需要用的就行。

1.3 GRAPH库的使用

GRAPH库是Microsoft封装的WEBAPI请求库,只要在【API权限】中权限配置合理,就可以正常使用GRAPH库提供的各种Microsoft API接口,使用上更像是一个SDK。同时Microsoft也提供了GRAPH库基础教程:

https://docs.microsoft.com/zh-cn/azure/active-directory/develop/quickstart-v2-android#how-the-sample-works

GRAPH库的使用更适合新项目的构建,因为它重构了返回数据。不仅仅是参数类型,信息条数都有调整,支持分页查询。具体的可以查看Microsoft提供的说明https://docs.microsoft.com/zh-cn/graph/api/user-list-messages?view=graph-rest-1.0

引用方法:

在app/build.gradle中配置如下引用

dependencies {
    implementation
'com.microsoft.graph:microsoft-graph:1.5.0'

在工程build.gradle中配置如下引用

repositories {

    jcenter()

可以在https://github.com/microsoftgraph/msgraph-sdk-java/releases 查看GRAPH库在github中的版本变更,及时更换到最新版本。

1.4 手动请求Microsoft API

如果不使用GRAPH库,我们还可以用传统的POST请求去调用相关的Microsoft API。前提是启用exchange ActiveSync对Exchange Server中邮箱的访问,一般启用操作不是我们考虑的事情。想了解的话,可以参看如下相关的网址https://docs.microsoft.com/zh-cn/exchange/clients/exchange-activesync/activesync-mailbox-access?view=exchserver-2019

发送一条成功的POST请求格式如下:

https://m.hotmail.com/Microsoft-Server-ActiveSync?Cmd=具体的命令名称&User=登录的账户&DeviceId=设备ID(可有可无)&DeviceType=Android

例如:https://m.hotmail.com/Microsoft-Server-ActiveSync?Cmd=FolderSync&User=lizg19920204%40outlook.com&DeviceId=1111111&DeviceType=Android

必要的请求头参数如下:

content-type

application/vnd.ms-sync.wbxml

 

wbxml格式,数据格式比较繁琐,不如GRAPH库返回的JSON格式简便

Cookie

数据格式:

DefaultAnchorMailbox=lizg19920204@outlook.com

 

注意,这里的cookie不是一定要加的,当你发起请求时,先不要添加cookie,如果响应结果码是451,才表示你需要cookie声明,此时可以从响应体中拿到它规定好的cookie值,直接利用即可。从我实践的结果来看,企业账号是不需要cookie,普通账号需要cookie。

 

MS-ASProtocolVersion

版本号,普通账号14.0或者2.0都可以,企业账号的话是14.1

Authorization

数据格式:

Bearer 从MSAL库中拿到的access_token

 

注意和Bearer之间是有空格的

 

可以先通过postman模拟一下请求,就可以熟悉响应体的结构了。URL中提到的Cmd可以通过如下网址查看:

https://docs.microsoft.com/en-us/openspecs/exchange_server_protocols/MS-OXPROTLP/229f77ea-6518-4fe7-84fe-bd535fc6c32e 这个网页里列举数个PDF文件,其中最重要的两个文件是[MS-ASHTTP]: Exchange ActiveSync: HTTP Protocol[MS-ASCMD]: Exchange ActiveSync: Command Reference Protocol ,这两个文件实际就是(二)和(三)中提到的大部分内容。大家可以自行研读下,没准能找到我认识不足的错误。

第三章 MSAL API和Microsoft API的使用本章围绕Microsoft Authentication Library的使用讲述如何调用Microsoft API。目录小节如下:1.1 MSAL库常用API的介绍1.2 scope的配置1.3 GRAPH库的使用1.4 手动请求Microsoft API其他相关文章:基于Microsoft的OAUTH2.0认证(一):基本概念梳理基于Microsoft的OAUTH2.0认证(二):MSAL库的引用和定制基于Mi 一个简单的微服务,用于从Azure Active Directory请求和缓存OAuth2授权令牌 此微服务从Azure Active Directory中抽象出对OAuth2访问令牌的检索,缓存和刷新,以便客户端应用程序仅需要提供用户名和密码即可检索有效的访问令牌。 这是一个示例请求: "username":"lucasalexander@somed365org.onmicrosoft.com", "password":"XXXXXXXX" 这是一个相应的示例响应: "accesstoken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImlCakwxUmNxemhp...", "expires_on": "1526704330", "action":
microsoft-oauth 非常早期的草案 - 目前未安装且不在上。 使用 Microsoft 帐户登录(例如 Hormail、Live.com 等、Outlook.com 等...)非常感谢人们 @khamoud 和 Jacob 使此功能得以启动 也基于和其他 OAuth 包 微软 OAuth2 的基本包...允许使用 Hotmail、Live.com、Outlook.com 帐户等登录 ServiceConfiguration.configurations.insert({ service: "microsoft", clientId: <your>, secret: <your>, //loginStyle: "redirect", display: "popup",
本博主要介绍microsoft 账号授权(OAuth 2.0)登入并获取用户信息的过程,因为写过google账号授权登入的过程,所以这里就简单介绍一下,google授权登入参考地址:http://www.cnblogs.com/JohnnyYin/p/3447217.html 1.去microsoft官网注册账号,注册完了账号再注册一个app,地址:https://account.li...
到了春暖花开的四月了,又要开始努力成长了。虽然口罩还没摘下,我们坚信疫情会过去,一切都会好起来的。 许久不写,介绍一篇“磨刀”文。 本篇讲述如何使用Visual Studio在桌面程序中调用Microsoft Graph,调用方式为简单易学的SDK方式,以C#为例。 首先你需要在Microsoft Identity平台注册一个应用程序,这个之前说过几遍就不再赘述了,忘了的点这里。 在Visual ...
转载:http://yuanyeqq.spaces.live.com/blog/cns!7F1C3C2FBD13154D!111.entry?_c11_blogpart_blogpart=blogview&_c=blogpart#permalink Windows批量升级补丁方法[域模式] Windows批量升级补丁方法 对于一个主要...
补充资料: 1、SpringBoot+Security 自定义DaoAuthenticationProvider,重写additionalAuthenticationChecks() 2、Spring Security# Multiple DaoAuthenticationProvider 也可以看看这个:基于...
1、概括在博客中,我们将讨论如何让Spring Security OAuth2实现使用JSON Web Tokens。2、Maven 配置首先,我们需要在我们的pom.xml中添加spring-security-jwt依赖项。<dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-
OAuth 2.0是一种开放标准的协议,用于授权第方应用程序使用用户在某些网站或服务中的资源,而不必向其提供用户名和密码。Shopify可以使用OAuth 2.0来允许第方应用程序访问商户的店铺数据。 要实现OAuth 2.0认证流程,我们首先需要在Shopify开发者门户中注册一个应用程序。在注册时,我们需要提供应用程序的名称、URI和回调URL。回调URL是当认证成功时Shopify将重定向到的URL。 为了开始OAuth认证流程,第方应用程序需要向客户端授权页面发出GET请求,该页面将提示用户允许应用程序访问他们的店铺数据。一旦用户同意授权,Shopify将会重定向回调URL,并附带一个授权码作为查询参数。 下一步,第方应用程序需要使用授权码向Shopify发送POST请求,以获取访问令牌。访问令牌是用于访问Shopify API的凭证。令牌使用JSON Web Token格式进行编码,并包含有关该令牌的元数据。 获取令牌后,第方应用程序就可以使用该令牌访问Shopify API并获取所需的数据。如果令牌过期或被撤销,则需要使用刷新令牌获取新令牌。 总之,通过实现OAuth 2.0认证流程,第方应用程序可以安全地访问Shopify API并获取商户的店铺数据,这为商户提供了更好的业务解决方案。