oauth2授权码模式demo

oauth2的运行流程在这里插入图片描述

  1. 用户打开客户端以后,客户端要求用户给予授权。
  2. 用户同意给予客户端授权。
  3. 客户端使用上一步获得的授权,向认证服务器申请令牌。
  4. 认证服务器对客户端进行认证以后,确认无误,同意发放令牌。
  5. 客户端使用令牌,向资源服务器申请获取资源。
  6. 资源服务器确认令牌无误,同意向客户端开放资源。

四种模式

  • 授权码模式(authorization code)
  • 简化模式(implicit)
  • 密码模式(resource owner password credentials)
  • 客户端模式(client credentials)

其中授权码模式是功能最完整、流程最严密的授权模式。
(1)用户访问客户端,后者将前者导向认证服务器,假设用户给予授权,认证服务器将用户导向客户端事先指定的"重定向URI"(redirection URI),同时附上一个授权码。

(2)客户端收到授权码,附上早先的"重定向URI",向认证服务器申请令牌:GET /oauth/token?response_type=code&client_id=demo&redirect_uri=重定向页面链接。请求成功返回code授权码,一般有效时间是10分钟。

(3)认证服务器核对了授权码和重定向URI,确认无误后,向客户端发送访问令牌(access token)和更新令牌(refresh token)。POST /oauth/token?response_type=authorization_code&code=SplxlOBeZQQYbYS6WxSbIA&redirect_uri=重定向页面链接。

直接开启代码

1、创建项目
父模块authorization-demo依赖springboot包含了两个子模块客户端client及模拟的微信认证服务端wechat
在这里插入图片描述

2、maven依赖

子模块client简单的web项目,仅仅依赖web组件

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

wechat模块加上security集成oauth2和简单页面模板引擎thymeleaf

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
            <version>2.3.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>

3、客户端代码

启动类配置一个RestTemplate和服务端微信通讯

package com.varcode.client;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;

/**
 * Created by hlin on 2021/2/18
 */
@SpringBootApplication
public class ClientApplication {

    public static void main(String[] args) {
        SpringApplication.run(ClientApplication.class, args);
    }

    @Bean
    RestTemplate restTemplate(){
        return new RestTemplate();
    }

}

一个回调controller,用于微信redirect的地址,获取授权码code

package com.varcode.client;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;

/**
 * Created by hlin on 2021/2/18
 */
@RestController
public class WechatController {

    @Autowired
    RestTemplate restTemplate;

    @RequestMapping("/client/wechat/redirect")
    public String getToken(@RequestParam String code){
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
        MultiValueMap<String, String> params= new LinkedMultiValueMap<>();
        params.add("grant_type","authorization_code");
        params.add("code",code);
        params.add("client_id","demo");
        params.add("client_secret","secret");
        params.add("redirect_uri","http://localhost:8081/client/wechat/redirect");
        HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(params, headers);
        ResponseEntity<String> response = restTemplate.postForEntity("http://localhost:8080/oauth/token", requestEntity, String.class);
        String token = response.getBody();
        return token;
    }

}

配置文件一个当前服务的端口 server.port=8081

4、wechat服务端代码

启动类

package com.varcode.wechat;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * Created by hlin on 2021/2/18
 */
@SpringBootApplication
public class WechatApplication {

    public static void main(String[] args) {
        SpringApplication.run(WechatApplication.class, args);
    }

}

security配置类

package com.varcode.wechat;

import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

/**
 * Created by hlin on 2021/2/18
 */
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    protected UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        // 创建两个微信用户
        manager.createUser(User.withUsername("wx_test17726").password("123456").authorities("USER").build());
        manager.createUser(User.withUsername("wx_test548956").password("123456").authorities("USER").build());
        return manager;
    }


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

    @Bean
    PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.
                requestMatchers()
                // 必须登录过的用户才可以进行 oauth2 的授权码申请
                .antMatchers("/", "/home","/login","/oauth/authorize")
                .and()
                .authorizeRequests()
                .anyRequest().permitAll()
                .and()
                .formLogin()
                .loginPage("/login")
                .and()
                .httpBasic()
                .disable()
                .exceptionHandling()
                .accessDeniedPage("/login?authorization_error=true")
                .and()
                .csrf()
                .requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))
                .disable();
    }
    
}

oauth2配置类

package com.varcode.wechat;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.approval.ApprovalStore;
import org.springframework.security.oauth2.provider.approval.TokenApprovalStore;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;

/**
 * Created by hlin on 2021/2/18
 */
@Configuration
public class OAuth2Configuration {

    private static final String WECHAT_RESOURCE_ID = "wechat";

    @Configuration
    @EnableResourceServer()
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources.resourceId(WECHAT_RESOURCE_ID).stateless(true);
//             如果关闭 stateless,则 accessToken 使用时的 session id 会被记录,后续请求不携带 accessToken 也可以正常响应
//            resources.resourceId(QQ_RESOURCE_ID).stateless(false);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {
            http
                    .requestMatchers()
                    // 保险起见,防止被主过滤器链路拦截
                    .antMatchers("/wechat/**").and()
                    .authorizeRequests()
                    .antMatchers("/wechat/user/**").access("#oauth2.hasScope('user_info')")
                    .antMatchers("/wechat/group/**").access("#oauth2.hasScope('group_info')")
                    .anyRequest().authenticated();
        }

    }

    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {

        @Autowired
        @Qualifier("authenticationManagerBean")
        private AuthenticationManager authenticationManager;

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {

            // @formatter:off
            clients.inMemory().withClient("demo")
                    .resourceIds(WECHAT_RESOURCE_ID)
                    .authorizedGrantTypes("authorization_code", "refresh_token", "implicit")
                    .authorities("ROLE_CLIENT")
                    .scopes("user_info", "group_info")
                    .secret("secret")
                    .redirectUris("http://localhost:8081/client/wechat/redirect")
                    .autoApprove(true)
                    .autoApprove("user_info");
                    //.and()多个资源认证的客户端
        }

        @Bean
        public ApprovalStore approvalStore() {
            TokenApprovalStore store = new TokenApprovalStore();
            store.setTokenStore(tokenStore());
            return store;
        }

        @Bean
        public TokenStore tokenStore() {
            return new InMemoryTokenStore();
            // 需要使用 redis 的话,放开这里
//            return new RedisTokenStore(redisConnectionFactory);
        }

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
            endpoints.tokenStore(tokenStore())
                    .authenticationManager(authenticationManager)
                    .allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
            ;
        }

        @Override
        public void configure(AuthorizationServerSecurityConfigurer oauthServer) {
            oauthServer.realm(WECHAT_RESOURCE_ID).allowFormAuthenticationForClients();
        }

    }

}

mvc配置,几个模板页面的映射地址

package com.varcode.wechat;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;

/**
 * Created by hlin on 2021/2/18
 */
@Configuration
public class SpringMvcConfiguration extends WebMvcConfigurerAdapter {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/home").setViewName("home");
        registry.addViewController("/").setViewName("home");
        registry.addViewController("/hello").setViewName("hello");
        registry.addViewController("/login").setViewName("login");
    }

}

服务端保护的资源接口

package com.varcode.wechat;

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


/**
 * Created by hlin on 2021/2/18
 */
@RestController
@RequestMapping("/wechat")
public class WechatController {

    @RequestMapping("/user/{id}")
    public String userInfo(@PathVariable String id){
        //模拟数据库查询结果
        if("wx_test17726".equals(id)){
            return "微信昵称:我要出去浪,微信号:wx_test17726,性别:男,年龄:22,头像:灰色头像";
        }
        if("wx_test548956".equals(id)){
            return "微信昵称:快来抓住我,微信号:wx_test548956,性别:女,年龄:16,头像:非主流头像";
        }
        return null;
    }

    @RequestMapping("/group")
    public String groupInfo(){
        //模拟数据库查询结果
        return "微信群名称:撸代码,群号:wxq_test145263,群人数:180";
    }

}

配置文件

server.port=8080
# security过滤器顺序稍微靠前
spring.security.filter.order=3
# 需要使用 redis 的话,放开这里
#spring.redis.host=127.0.0.1
#spring.redis.database=0

三个静态测试页面,templates目录下

hello.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Hello Oauth2!</title>
    </head>
    <body>
        <h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>
        <form th:action="@{/logout}" method="post">
            <input type="submit" value="Sign Out"/>
        </form>
    </body>
</html>

home.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Oauth2 Example Home Page</title>
    </head>
    <body>
        <h1>Start!</h1>
        
        <p>Click <a th:href="@{/hello}">here</a> to see a greeting.</p>
    </body>
</html>

login.html

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Oauth2 Example Login Page</title>
    </head>
    <body>
        <div th:if="${param.error}">
            Invalid username and password.
        </div>
        <div th:if="${param.logout}">
            You have been logged out.
        </div>
        <form th:action="@{/login}" method="post">
            <div><label> User Name : <input type="text" name="username"/> </label></div>
            <div><label> Password: <input type="password" name="password"/> </label></div>
            <div><input type="submit" value="Sign In"/></div>
        </form>
    </body>
</html>

5、测试认证流程

分别启动client和wechat服务

1、浏览器访问地址:http://localhost:8080/wechat/user/wx_test17726
需要认证提示

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<oauth>
<error_description>Full authentication is required to access this resource</error_description>
<error>unauthorized</error>
</oauth>

2、获取授权码
http://localhost:8080/oauth/authorize?client_id=demo&response_type=code&redirect_uri=http://localhost:8081/client/wechat/redirect跳转登录页面,根据配置是需要登录才可以获取授权码信息的,相当于用户手动允许登录授权
在这里插入图片描述
3、输入配置的模拟用户名和密码wx_test17726,123456 进入client回调redirect接口获取access_token:

{"access_token":"8a087fae-ae29-4235-ba94-5cd29141e485","token_type":"bearer","refresh_token":"bb1761e7-4ce9-479b-8430-790aaf63cd52","expires_in":43199,"scope":"user_info group_info"}

redirect_uri 执行 302 重定向,并且带上生成的 code,注意重定向到的是 8001 端口,这个时候已经是另外一个应用了,即client
地址栏code申请一次过期,access_token有时效,当天申请也是有次数限制,需客户端加入缓存redis等
在这里插入图片描述
4、携带access_token访问受保护的资源接口
在这里插入图片描述
在这里插入图片描述
至此整个oauth2授权码模式流程结束!

  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值