18
2023
04
14:14:55

使用OAuth2实现授权服务



推荐点击下面图片,通过本站淘宝优惠价购买:

image.png

OAuth 2发明之初是为了解决登录认证过程中的安全性问题,使用“委托”的形式使第三方应用获得数据权限及功能。OAuth 2.0协议中,使用访问令牌ACCESS_TOKEN代替传统的账号密码,提高了互联网环境下的安全性。

OAuth 2共分为四种角色:

  • 授权服务:功能开放平台

  • 资源所有者:用户

  • 受保护资源:接口提供方

  • 客户端:第三方软件即接口调用方

实则授权服务和受保护资源可以部署在同一服务器上,也可以部署在不同服务上,因为两种角色是属于同一开发团队。

在微服务环境下使用Spring OAuth 2实现授权服务流程,需要分成三个模块:

server端:授权服务端,配置OAuth 2授权服务器信息,负责生成授权码及访问令牌等

resource端:接口提供方,负责解析授权令牌、鉴权、数据提供

client端:第三方应用,负责调用第三方数据

准备工作

一、数据库脚本
SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;-- ------------------------------ Table structure for authorities-- ----------------------------DROP TABLE IF EXISTS `authorities`;CREATE TABLE `authorities`  (
  `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `authority` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  UNIQUE INDEX `ix_auth_username`(`username`, `authority`) USING BTREE,
  CONSTRAINT `fk_authorities_users` FOREIGN KEY (`username`) REFERENCES `users` (`username`) ON DELETE RESTRICT ON UPDATE RESTRICT) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;-- ------------------------------ Records of authorities-- ----------------------------INSERT INTO `authorities` VALUES ('reader', 'READ');INSERT INTO `authorities` VALUES ('writer', 'READ,WRITE');-- ------------------------------ Table structure for oauth_approvals-- ----------------------------DROP TABLE IF EXISTS `oauth_approvals`;CREATE TABLE `oauth_approvals`  (
  `userId` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `clientId` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `partnerKey` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `scope` varchar(256) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `status` varchar(10) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `expiresAt` datetime(0) NULL DEFAULT NULL,
  `lastModifiedAt` datetime(0) NULL DEFAULT NULL) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;-- ------------------------------ Records of oauth_approvals-- ----------------------------INSERT INTO `oauth_approvals` VALUES ('reader', 'userservice3', NULL, 'FOO', 'APPROVED', '2022-09-30 06:53:34', '2022-08-31 06:53:34');INSERT INTO `oauth_approvals` VALUES ('writer', 'userservice3', NULL, 'FOO', 'APPROVED', '2022-09-30 13:56:15', '2022-08-31 13:56:15');-- ------------------------------ Table structure for oauth_client_details-- ----------------------------DROP TABLE IF EXISTS `oauth_client_details`;CREATE TABLE `oauth_client_details`  (
  `client_id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `resource_ids` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `client_secret` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `scope` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `authorized_grant_types` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `web_server_redirect_uri` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `authorities` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `access_token_validity` int(0) NULL DEFAULT NULL,
  `refresh_token_validity` int(0) NULL DEFAULT NULL,
  `additional_information` varchar(4096) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `autoapprove` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`client_id`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;-- ------------------------------ Records of oauth_client_details-- ----------------------------INSERT INTO `oauth_client_details` VALUES ('userservice1', 'userservice', '1234', 'FOO', 'password,refresh_token', '', 'READ,WRITE', 7200, NULL, NULL, 'true');INSERT INTO `oauth_client_details` VALUES ('userservice2', 'userservice', '1234', 'FOO', 'client_credentials,refresh_token', '', 'READ,WRITE', 7200, NULL, NULL, 'true');INSERT INTO `oauth_client_details` VALUES ('userservice3', 'userservice', '1234', 'FOO', 'authorization_code,refresh_token', 'https://baidu.com,https://tev-competition-admin.qstcloud.net,http://localhost:5083/ui/login,http://localhost:8083/ui/login,http://localhost:5082/ui/remoteCall', 'READ,WRITE', 7200, NULL, NULL, 'false');-- ------------------------------ Table structure for oauth_code-- ----------------------------DROP TABLE IF EXISTS `oauth_code`;CREATE TABLE `oauth_code`  (
  `code` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `authentication` blob NULL) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;-- ------------------------------ Records of oauth_code-- ------------------------------ ------------------------------ Table structure for users-- ----------------------------DROP TABLE IF EXISTS `users`;CREATE TABLE `users`  (
  `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `password` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `enabled` tinyint(1) NOT NULL,
  PRIMARY KEY (`username`) USING BTREE) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;-- ------------------------------ Records of users-- ----------------------------INSERT INTO `users` VALUES ('reader', '$2a$04$C6pPJvC1v6.enW6ZZxX.luTdpSI/1gcgTVN7LhvQV6l/AfmzNU/3i', 1);INSERT INTO `users` VALUES ('writer', '$2a$04$M9t2oVs3/VIreBMocOujqOaB/oziWL0SnlWdt8hV4YnlhQrORA0fS', 1);SET FOREIGN_KEY_CHECKS = 1;
二、项目框架

搭建项目父依赖,并创建三个模块:

  • cloud-oauth2-client

  • cloud-oauth2-server

  • cloud-oauth2-userservice

授权服务器

引入依赖
   <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
服务端配置

授权服务配置需要继承自org.springframework.security.oauth2.config.annotation.web.configuration包下的AuthorizationServerConfigurerAdapter类,主要配置了用户信息来源、访问权限配置、Token配置。

@Configuration//开启授权服务器@EnableAuthorizationServer 
public class OAuth2ServerConfiguration extends AuthorizationServerConfigurerAdapter {
    @Autowired    private DataSource dataSource;
    @Autowired    private AuthenticationManager authenticationManager;

    /**
     * 我们配置了使用数据库来维护客户端信息。虽然在各种Demo中我们经常看到的是在内存中维护客户端信息,通过配置直接写死在这里。
     * 但是,对于实际的应用我们一般都会用数据库来维护这个信息,甚至还会建立一套工作流来允许客户端自己申请ClientID,实现OAuth客户端接入的审批。
     * @param clients
     * @throws Exception
     */
    @Override    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.jdbc(dataSource);
    }

    /**
     * 这里干了两件事儿。首先,打开了验证Token的访问权限(以便之后我们演示)。
     * 然后,允许ClientSecret明文方式保存,并且可以通过表单提交(而不仅仅是Basic Auth方式提交),之后会演示到这个。
     * @param security
     * @throws Exception
     */
    @Override    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.checkTokenAccess("permitAll()")
                .allowFormAuthenticationForClients().passwordEncoder(NoOpPasswordEncoder.getInstance());
    }

    /**
     * 干了以下4件事儿:
     * 1. 配置我们的令牌存放方式为JWT方式,而不是内存、数据库或Redis方式。
     * JWT是Json Web Token的缩写,也就是使用JSON数据格式包装的令牌,由.号把整个JWT分隔为头、数据体、签名三部分。
     * JWT保存Token虽然易于使用但是不是那么安全,一般用于内部,且需要走HTTPS并配置比较短的失效时间。
     * 2. 配置JWT Token的非对称加密来进行签名
     * 3. 配置一个自定义的Token增强器,把更多信息放入Token中
     * 4. 配置使用JDBC数据库方式来保存用户的授权批准记录
     * @param endpoints
     * @throws Exception
     */
    @Override    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        tokenEnhancerChain.setTokenEnhancers(
                Arrays.asList(tokenEnhancer(), jwtTokenEnhancer()));

        endpoints.approvalStore(approvalStore())
                .authorizationCodeServices(authorizationCodeServices())
                .tokenStore(tokenStore())
                .tokenEnhancer(tokenEnhancerChain)
                .authenticationManager(authenticationManager);
    }

    /**
     * 使用JDBC数据库方式来保存授权码
     * @return
     */
    @Bean    public AuthorizationCodeServices authorizationCodeServices() {
        return new JdbcAuthorizationCodeServices(dataSource);
    }

    /**
     * 使用JWT存储
     * @return
     */
    @Bean    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtTokenEnhancer());
    }

    /**
     * 使用JDBC数据库方式来保存用户的授权批准记录
     * @return
     */
    @Bean    public JdbcApprovalStore approvalStore() {
        return new JdbcApprovalStore(dataSource);
    }

    /**
     * 自定义的Token增强器,把更多信息放入Token中
     * @return
     */
    @Bean    public TokenEnhancer tokenEnhancer() {
        return new CustomTokenEnhancer();
    }

    /**
     * 配置JWT使用非对称加密方式来验证
     * @return
     */
    @Bean    protected JwtAccessTokenConverter jwtTokenEnhancer() {
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "mySecretKey".toCharArray());
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("jwt"));
        return converter;
    }

    /**
     * 配置登录页面的视图信息(其实可以独立一个配置类,这样会更规范)
     */
    @Configuration    static class MvcConfig implements WebMvcConfigurer {
        @Override        public void addViewControllers(ViewControllerRegistry registry) {
            registry.addViewController("login").setViewName("login");
        }
    }}
自定义Token增强器

主要用于将自定义的更多用户信息放入Token中。

public class CustomTokenEnhancer implements TokenEnhancer {

    @Override    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        Authentication userAuthentication = authentication.getUserAuthentication();
        if (userAuthentication != null) {
            Object principal = authentication.getUserAuthentication().getPrincipal();
            //把用户标识嵌入JWT Token中去(Key是userDetails)
            Map<String, Object> additionalInfo = new HashMap<>();
            additionalInfo.put("userDetails", principal);
            ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
        }
        return accessToken;
    }}
安全配置
@Configurationpublic class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired    private DataSource dataSource;

    @Override
    @Bean    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 配置用户账户的认证方式。显然,我们把用户存在了数据库中希望配置JDBC的方式。
     * 此外,我们还配置了使用BCryptPasswordEncoder哈希来保存用户的密码(生产环境中,用户密码肯定不能是明文保存的)
     * @param auth
     * @throws Exception
     */
    @Override    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.jdbcAuthentication()
                .dataSource(dataSource)
                .passwordEncoder(new BCryptPasswordEncoder());
    }

    /**
     * 开放/login和/oauth/authorize两个路径的匿名访问。前者用于登录,后者用于换授权码,这两个端点访问的时机都在登录之前。
     * 设置/login使用表单验证进行登录。
     * @param http
     * @throws Exception
     */
    @Override    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/login", "/oauth/authorize")
                .permitAll()
                .anyRequest().authenticated()
                .and()
                .formLogin().loginPage("/login");
    }}

登录页html放在templates目录下:

<body class="uk-height-1-1"><div class="uk-vertical-align uk-text-center uk-height-1-1">
    <div class="uk-vertical-align-middle" style="width: 250px;">
        <h1>Login Form</h1>

        <p class="uk-text-danger" th:if="${param.error}">
            用户名或密码错误...
        </p>

        <form class="uk-panel uk-panel-box uk-form" method="post" th:action="@{/login}">
            <div class="uk-form-row">
                <input class="uk-width-1-1 uk-form-large" type="text" placeholder="Username" name="username"
                       value="reader"/>
            </div>
            <div class="uk-form-row">
                <input class="uk-width-1-1 uk-form-large" type="password" placeholder="Password" name="password"
                       value="reader"/>
            </div>
            <div class="uk-form-row">
                <button class="uk-width-1-1 uk-button uk-button-primary uk-button-large">Login</button>
            </div>
        </form>

    </div></div></body>

受保护资源服务器

引入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-oauth2</artifactId></dependency><dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId></dependency>
受保护资源配置

创建核心资源服务器配置类。

  • 硬编码了资源服务器的 ID 为 userservice

  • 现在我们使用的是不落数据库的JWT方式 + 非对称加密,需要通过本地公钥进行验证,因此在这里我们配置了公钥的路径。

公钥和密钥有多种配置方式,可以通过读取文件、读取配置文件等方式,只需要保证公钥和私钥配对即可,在本demo中可以直接复用public.cert

@Configuration
@EnableResourceServer //启用资源服务器@EnableGlobalMethodSecurity(prePostEnabled = true) //启用方法注解方式来进行权限控制public class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

    /**
     * 声明了资源服务器的ID是userservice,声明了资源服务器的TokenStore是JWT
     * @param resources
     * @throws Exception
     */
    @Override    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.resourceId("userservice").tokenStore(tokenStore());
    }

    /**
     * 配置TokenStore
     *
     * @return
     */
    @Bean    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    /**
     * 配置公钥
     * @return
     */
    @Bean    protected JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        Resource resource = new ClassPathResource("public.cert");
        String publicKey = null;
        try {
            publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
        } catch (IOException e) {
            e.printStackTrace();
        }
        converter.setVerifierKey(publicKey);
        return converter;
    }

    /**
     * 配置了除了/user路径之外的请求可以匿名访问
     * @param http
     * @throws Exception
     */
    @Override    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/user/**").authenticated()
                .anyRequest().permitAll();
    }}

启动项目Server、Resource。

测试

各大开放平台都是推荐使用授权码许可流程,无论是网页版的 Web 应用程序,还是移动应用程序。本次仅演示授权码模式登录。

第一步,打开浏览器访问地址:

http://localhost:8080/oauth/authorize?response_type=code&client_id=userservice3&redirect_uri=https://baidu.com

注意,客户端跳转地址需要和数据库中配置的一致(百度的 URL https://baidu.com我们之前已经在数据库中有配置了)。访问后页面会直接跳转到登录界面,我们使用用户名“reader”、密码“reader”来登录,点击批准,可以发现页面重定向到百度页,并且地址栏最后出现了授权码。

第二步,使用授权码获取访问令牌ACCESS_TOKEN

http://localhost:8080/oauth/token?grant_type=authorization_code&client_id=userservice3&client_secret=1234&code=XKkHGY&redirect_uri=https://baidu.com

redirect_uri后的参数需要在数据库oauth_client_details表中配置,不同数值间用英文逗号分隔

Gitee地址: https://gitee.com/FirstMrRight/oauth2.git

从 OAuth2 服务器获取授权授权

搭建好了基于 OWIN 的 OAuth2 服务器之后, 接下来就是如何从服务器取得授权了, 下面就介绍如何实现 OAuth2 定义的四种授权方式。

授权码授权 (Authorization Code Grant)

授权码授权针对机密的客户端优化, 可以同时获取访问凭据 (access token) 和刷新凭据 (refresh token) , 因为是基于 HTTP 重定向的方式, 所以客户端必须能够操纵资源所有者的用户代理(通常是浏览器)并且能够接收从授权服务器重定向过来的请求。

在实现上使用开源的 DotNetOpenAuth 来简化实现代码, DotNetOpenAuth 可以通过 NuGet 获取, 示例代码如下:

// init a new oauth web server client;var authServer = new AuthorizationServerDescription {
    AuthorizationEndpoint = new Uri(Paths.AuthorizePath),
    TokenEndpoint = new Uri(Paths.TokenPath)};var webServerClient = new WebServerClient(authServer, clientId, clientSecret);// redirect user user-agent to authorization endpoint;var userAuthorization = webServerClient.PrepareRequestUserAuthorization(new[] { "bio", "notes" });userAuthorization.Send(HttpContext);Response.End();// get access token from request (redirect from oauth server)var authorizationState = webServerClient.ProcessUserAuthorization(Request);if (authorizationState != null) {
    ViewBag.AccessToken = authorizationState.AccessToken;
    ViewBag.RefreshToken = authorizationState.RefreshToken;
    ViewBag.Action = Request.Path;}//refresh tokenvar state = new AuthorizationState {
    AccessToken = Request.Form["AccessToken"],
    RefreshToken = Request.Form["RefreshToken"]};if (webServerClient.RefreshAuthorization(state)) {
    ViewBag.AccessToken = state.AccessToken;
    ViewBag.RefreshToken = state.RefreshToken;}// call protected user resourcevar client = new HttpClient(webServerClient.CreateAuthorizingHandler(accessToken));var body = await client.GetStringAsync(new Uri(Paths.ResourceUserApiPath));ViewBag.ApiResponse = body;

隐式授权 (Implicit Grant)

隐式授权为已知的公开客户端优化, 用于客户端操作一个特定的重定向地址, 只能获取访问凭据 (access token) , 不支持刷新凭据 (refresh token) 。 客户端通常在浏览器内用 Javascript 实现。

因为是基于 HTTP 重定向的方式, 所以客户端必须能够操纵资源所有者的用户代理(通常是浏览器)并且能够接收从授权服务器重定向过来的请求。

与授权码授权方式不同的是, 客户端不需要为授权和访问凭据分别发送单独的请求, 可以直接从授权请求获取访问凭据。

隐式授权不包括客户端授权, 依赖资源所有者(用户)的现场判断以及客户端重定向地址, 由于访问凭据是在 URL 中编码的, 所以有可能会暴漏给用户或客户端上的其它应用。

由于这种授权方式一般是通过浏览器实现的, 所以就不用依赖 DotNetOpenAuth 了, 只需要 Javascript 就行了, 示例代码如下:

// index.htmlvar authorizeUri = '@(Paths.AuthorizePath)';var tokenUri = '@(Paths.TokenPath)';var apiUri = '@Paths.ResourceUserApiPath';var clientId = '@clientId';var returnUri = '@clientRedirectUrl';var nonce = 'my-nonce';$('#authorize').click(function () {
    // build redirect url
    var uri = addQueryString(authorizeUri, {
        'client_id': clientId,
        'redirect_uri': returnUri,
        'state': nonce,
        'scope': 'bio notes',
        'response_type': 'token'
    });
    // login callback
    window.oauth = {};
    window.oauth.signin = function (data) {
        if (data.state !== nonce) {
            return;
        }
        $('#accessToken').val(data.access_token);
    }
    // open login.html in a new window.
    window.open(uri, 'authorize', 'width=640,height=480');});// add query string to urifunction addQueryString(uri, parameters) {
    var delimiter = (uri.indexOf('?') == -1) ? '?' : '&';
    for (var parameterName in parameters) {
        var parameterValue = parameters[parameterName];
        uri += delimiter + encodeURIComponent(parameterName) + '=' + encodeURIComponent(parameterValue);
        delimiter = '&';
    }
    return uri;}// login.html// get fragment and call opener's signin function.var fragments = getFragment();if (window.opener && window.opener.oauth && window.opener.oauth.signin) {
    window.opener.oauth.signin(fragments);}window.close();// get fragment from window urifunction getFragment() {
    if (window.location.hash.indexOf("#") === 0) {
        return parseQueryString(window.location.hash.substr(1));
    } else {
        return {};
    }}// parse query string to object;function parseQueryString(queryString) {
    var data = {}, pairs, pair, separatorIndex, escapedKey, escapedValue, key, value;

    if (queryString === null) {
        return data;
    }

    pairs = queryString.split("&");

    for (var i = 0; i < pairs.length; i++) {
        pair = pairs[i];
        separatorIndex = pair.indexOf("=");

        if (separatorIndex === -1) {
            escapedKey = pair;
            escapedValue = null;
        } else {
            escapedKey = pair.substr(0, separatorIndex);
            escapedValue = pair.substr(separatorIndex + 1);
        }

        key = decodeURIComponent(escapedKey);
        value = decodeURIComponent(escapedValue);

        data[key] = value;
    }

    return data;}

资源所有者密码凭据授权 (Resource Owner Password Credentials Grant)

资源所有者密码凭据授权适用于那些被充分信任的应用, 比如设备操作系统或者权限很高的应用。 授权服务器启用这类授权是要格外注意, 只能在其它授权方式不能用的时候才使用这种授权方式。

这种授权方式适用于能够取得用户的凭据 (通常是通过可交互的表单) 的应用, 也可以用于迁移现有的那些需要直接授权 (HTTP Basic 或 Digest ) 的应用, 将保存的用户凭据改为保存访问凭据 (access token) 。

对于 DotNetOpenAuth 来说, 这种授权也是十分容易实现的, 示例代码如下:

// create auth server description var authServer = new AuthorizationServerDescription {
    AuthorizationEndpoint = new Uri(Paths.AuthorizePath),
    TokenEndpoint = new Uri(Paths.TokenPath)};// create web server clientvar webServerClient = new WebServerClient(authServer, clientId, clientSecret);// use user name and password to exchange access token;var state = webServerClient.ExchangeUserCredentialForToken(
    username, password,
    new[] {"scope1", "scope2", "scope3"});// get access token;var token = state.AccessToken;

客户端凭据授权 (Client Credentials Grant)

客户端凭据授权是指客户端可以只通过客户端自己的凭据 (client_id 和 client_secret) (或者其它方式的认证) 来获取访问凭据, 客户端可以根据自己的需要来访问受保护的资源, 或者资源所有者已经访问过认证服务器时, 才能使用这种授权方式。 只有对完全受信任的客户端才能使用这种授权方式, 因为对受保护的资源方来说, 认证信息的内容是客户端程序的凭据, 而不是资源所有者的凭据。

DotNetOpenAuth 也支持这种授权方式, 示例代码如下:

// create auth server description var authServer = new AuthorizationServerDescription {
    AuthorizationEndpoint = new Uri(Paths.AuthorizePath),
    TokenEndpoint = new Uri(Paths.TokenPath)};// create web server clientvar webServerClient = new WebServerClient(authServer, clientId, clientSecret);// get client access token;var state = webServerClient.GetClientAccessToken(
  new[] { "test1", "test2", "test3" });// get access token;var token = state.AccessToken;

使用访问凭据访问受保护的资源

上面介绍的都是如何取得访问凭据 (access_token) , 拿到了访问凭据之后如何来使用呢? 对于使用微软的 OWIN 中间件 Microsoft.Owin.Security.OAuth 搭建的服务器来说, 需要设置 HTTP 请求的 Authorization 标头为 Bearer {access_token} 就可以了, 这个属于 OAuth 的规范之内了, 示例代码如下:

使用 jQuery 的 Ajax 请求时, 示例代码如下:

var accessToken = '@AccessToken';$.ajax({
    url: '@ResourcePath',
    beforeSend: function(jqr) {
        jqr.setRequestHeader('Authorization', 'Bearer ' + accessToken);
    }}).done(function (data) {
    // other code here.});

使用其它语言的代码与上面的 js 代码大同小异,上面只是一些代码片段, 在 github 上有完整的项目代码, 不清楚的地方可以直接查看源代码。


本文链接:https://www.hqyman.cn/post/3904.html 非本站原创文章欢迎转载,原创文章需保留本站地址!

分享到:





休息一下,本站随机推荐观看栏目:


« 上一篇 下一篇 »

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

您的IP地址是: