Spring Cloud系列 Spring Cloud OAuth2授权码模式(authorization code)

OAuth 2 有四种授权模式,分别是授权码模式(authorization code)、简化模式(implicit)、密码模式(resource owner password credentials)、客户端模式(client credentials),具体 OAuth2 是什么,可以参考这篇文章(http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html)

实现统一认证功能

本篇先介绍密码模式实现流程图,以微信授权登录为例。第三方应用获取微信用户信息。在微信授权登录的模式,需要到微信官方备案该应用。

Spring Cloud系列 Spring Cloud OAuth2授权码模式(authorization code)_第1张图片

1.用户通过手机微信扫码授权后台会发起(https://open.weixin.qq.com/connect/oauth2/authorize?appid=wxf0e81c3bee622d60&redirect_uri=http:open.winning.com/index&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect)访问微信授权服务器

2.微信服务器验证通过后跳转(http:open.winning.com/index?code=DX3FCW)第三方应用并带有code参数

3.第三方服务获取后code参数后,继续请求微信认证服务器((https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID
&secret=SECRET&code=DX3FCW&grant_type=authorization_code))获取token。通过验证和后返回token。

4.第三方服务获取token后,向微信用户信息服务发起请求,获取用户信息。第三方成功获取用户信息后。用户登录第三方应用成功。

以上只是一种权限认证的思路流程。以下咱们按照以上的思路实现自己的认证服务

本篇采用的是spring cloud微服务。并有双注册中心。所以要搭建双注册中心。目录结构

在一台电脑实现双注册中心需要修改本机hosts文件,如下,添加127.0.0.1 peer1    127.0.0.1 peer2

Spring Cloud系列 Spring Cloud OAuth2授权码模式(authorization code)_第2张图片

注册中心peer1

Spring Cloud系列 Spring Cloud OAuth2授权码模式(authorization code)_第3张图片

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class GoodluckEurekaApplication {

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

}
server.port=1111
spring.application.name=hello-service1

#关闭保护机制
eureka.server.enable-self-preservation=false
eureka.instance.hostname=peer1
#由于该应用是注册中心,false:代表不向注册中心注册自己;true:代表注册自己
eureka.client.register-with-eureka=false
#是否启动检测服务,由于注册中心的职责是维护服务实例,所以它不需要检服务
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://peer2:1112/eureka/


	4.0.0
	
		org.springframework.boot
		spring-boot-starter-parent
		2.0.0.RELEASE
		 
	
	com.goodluck
	goodluck-eureka
	0.0.1-SNAPSHOT
	goodluck-eureka
	Demo project for Spring Boot

	
		1.8
	

	
		
			org.springframework.boot
			spring-boot-starter-thymeleaf
		
		
			org.springframework.boot
			spring-boot-starter-web
		

		
			org.springframework.boot
			spring-boot-starter-test
			test
			
				
					org.junit.vintage
					junit-vintage-engine
				
			
		

		
		
			org.springframework.cloud
			spring-cloud-starter-netflix-eureka-server
		

	


	
	
		
			
				org.springframework.cloud
				spring-cloud-dependencies
				Finchley.RELEASE
				pom
				import
			
		
	

	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		
	


 注册中心peer2(在peer1的基础上修改配置文件如下)

spring.application.name=eureka-server
server.port=1112

#关闭保护机制
eureka.server.enable-self-preservation=false
eureka.instance.hostname=peer2
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.service-url.defaultZone=http://peer1:1111/eureka/

 

按照上面思路咱们实现自己的认证服务(oauth-server)。

1.这是我的项目结构

Spring Cloud系列 Spring Cloud OAuth2授权码模式(authorization code)_第4张图片

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class DemoApplication {

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

}
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

/**
 * JwtTokenConfig
 *
 * @author fengzheng
 * @date 2019/10/12
 */
@Configuration
public class JwtTokenConfig {

    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();
        accessTokenConverter.setSigningKey("dev");
        return accessTokenConverter;
    }
}
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by Administrator on 2020-06-10.
 */
@Slf4j
@Component(value = "kiteUserDetailsService")
public class KiteUserDetailsService  implements UserDetailsService {

    @Autowired
    private PasswordEncoder passwordEncoder;

    private  final Logger logger = LoggerFactory.getLogger(KiteUserDetailsService.class);

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        logger.info("usernameis:" + username);
        // 查询数据库操作
        if(!username.equals("admin")){
            throw new UsernameNotFoundException("the user is not found");
        }else{
            // 用户角色也应在数据库中获取
            String role = "ROLE_ADMIN";
            List authorities = new ArrayList<>();
            authorities.add(new SimpleGrantedAuthority(role));
            // 线上环境应该通过用户名查询数据库获取加密后的密码
            String password = passwordEncoder.encode("123456");
            // 返回默认的 User
            // return new org.springframework.security.core.userdetails.User(username,password, authorities);

            // 返回自定义的 KiteUserDetails
            User user = new User(username,password,authorities);
            return user;
        }
    }
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
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.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;

import javax.sql.DataSource;
import java.util.ArrayList;

/**
 * Created by Administrator on 2020-06-10.
 */
@Configuration
@EnableAuthorizationServer
public class OAuth2Config  extends AuthorizationServerConfigurerAdapter {

    @Autowired
    public PasswordEncoder passwordEncoder;

    @Autowired
    public UserDetailsService kiteUserDetailsService;

    @Autowired
    private AuthenticationManager authenticationManager;

//    @Autowired
//    private TokenStore redisTokenStore;

//    @Autowired
//    private DataSource dataSource;

    @Autowired
    private TokenStore jwtTokenStore;

    @Autowired
    private JwtAccessTokenConverter jwtAccessTokenConverter;

    //@Autowired
    //private TokenEnhancer jwtTokenEnhancer;

    @Override
    public void configure(final AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        /**
         * 普通 jwt 模式
         */
         endpoints.tokenStore(jwtTokenStore)
                .accessTokenConverter(jwtAccessTokenConverter)
                .userDetailsService(kiteUserDetailsService)
                /**
                 * 支持 password 模式
                 */
                .authenticationManager(authenticationManager);

        /**
         * jwt 增强模式
         */
       // TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
      //  List enhancerList = new ArrayList<>();
      //  enhancerList.add(jwtTokenEnhancer);
      //  enhancerList.add(jwtAccessTokenConverter);
      //  enhancerChain.setTokenEnhancers(enhancerList);
      //  endpoints.tokenStore(jwtTokenStore)
         //       .userDetailsService(kiteUserDetailsService)
                /**
                 * 支持 password 模式
                 */
            //  .authenticationManager(authenticationManager)
            //    .tokenEnhancer(enhancerChain)
            //    .accessTokenConverter(jwtAccessTokenConverter);


        /**
         * redis token 方式
         */
//        endpoints.authenticationManager(authenticationManager)
//                .tokenStore(redisTokenStore)
//                .userDetailsService(kiteUserDetailsService);

    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //clients.jdbc(dataSource);

        clients.inMemory()
                .withClient("order-client")
                .secret(passwordEncoder.encode("order-secret-8888"))
                //.secret("order-secret-8888")
                .authorizedGrantTypes("refresh_token", "authorization_code", "password")
                .accessTokenValiditySeconds(3600)
                .scopes("all")
                .and()
                .withClient("user-client")
                .secret(passwordEncoder.encode("user-secret-8888"))
                .authorizedGrantTypes("refresh_token", "authorization_code", "password")
                .accessTokenValiditySeconds(3600)
                .scopes("all")
                .and()
                .withClient("code-client")
                .secret(passwordEncoder.encode("code-secret-8888"))
                .authorizedGrantTypes("refresh_token","authorization_code")
                .redirectUris("http://localhost:6102/client-authcode/login")
                .accessTokenValiditySeconds(3600)
                .scopes("all");
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients();
        security.checkTokenAccess("isAuthenticated()");
        security.tokenKeyAccess("isAuthenticated()");
    }
}
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.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * Created by Administrator on 2020-06-10.
 */
@EnableWebSecurity
public class WebSecurityConfig  extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

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

    /**
     * 允许匿名访问所有接口 主要是 oauth 接口
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.formLogin()
                .and()
                .authorizeRequests()
                .antMatchers("/**").permitAll();
    }
}
spring.application.name=auth-server
server.port=6001


### radm database
#spring.datasource.url=jdbc:sqlserver://localhost:1433;databasename=UI_MMS
#spring.datasource.username=sa
#spring.datasource.password=123
#spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver


management.endpoint.health.enabled=true

eureka.instance.hostname=spring-cloud-ureka-server-one
eureka.client.service-url.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/


	4.0.0
	
		org.springframework.boot
		spring-boot-starter-parent
		2.0.0.RELEASE
		 
	
	com.example
	demo
	0.0.1-SNAPSHOT
	demo
	Demo project for Spring Boot

	
		1.8
	


	
		
			org.springframework.boot
			spring-boot-starter-web
		

		
			org.springframework.boot
			spring-boot-starter-test
			test
			
				
					org.junit.vintage
					junit-vintage-engine
				
			
		


		
		
			org.springframework.cloud
			spring-cloud-starter-netflix-eureka-client
		

		
			org.springframework.cloud
			spring-cloud-starter-oauth2
		

		
			org.springframework.boot
			spring-boot-starter-actuator
		

		
			org.projectlombok
			lombok
		

		
			
			
		

		
			
			
		


	


	
	
		
			
				org.springframework.cloud
				spring-cloud-dependencies
				Finchley.RELEASE
				pom
				import
			
		
	



	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		
	



第三方应用代码结构

Spring Cloud系列 Spring Cloud OAuth2授权码模式(authorization code)_第5张图片

import com.fasterxml.jackson.databind.ObjectMapper;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import okhttp3.*;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

import java.nio.charset.StandardCharsets;
import java.util.Map;

/**
 * Created by Administrator on 2020-06-15.
 */
@Controller
public class CodeClientController {
    /**
     * 用来展示index.html 模板
     * @return
     */
    @GetMapping(value = "index")
    public String index(){
        return "index";
    }

    @GetMapping(value = "login")
    public Object login(String code,Model model) {
        String tokenUrl = "http://localhost:6001/oauth/token";
        OkHttpClient httpClient = new OkHttpClient();
        RequestBody body = new FormBody.Builder()
                .add("grant_type", "authorization_code")
                .add("client", "code-client")
                .add("redirect_uri","http://localhost:6102/client-authcode/login")
                .add("code", code)
                .build();

        Request request = new Request.Builder()
                .url(tokenUrl)
                .post(body)
                .addHeader("Authorization", "Basic Y29kZS1jbGllbnQ6Y29kZS1zZWNyZXQtODg4OA==")
                .build();
        try {
            Response response = httpClient.newCall(request).execute();
            String result = response.body().string();
            ObjectMapper objectMapper = new ObjectMapper();
            Map tokenMap = objectMapper.readValue(result,Map.class);
            String accessToken = tokenMap.get("access_token").toString();
            Claims claims = Jwts.parser()
                    .setSigningKey("dev".getBytes(StandardCharsets.UTF_8))
                    .parseClaimsJws(accessToken)
                    .getBody();
            String userName = claims.get("user_name").toString();
            model.addAttribute("username", userName);
            model.addAttribute("accessToken", result);
            return "index";
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    @org.springframework.web.bind.annotation.ResponseBody
    @GetMapping(value = "get")
    @PreAuthorize("hasAnyRole('ROLE_ADMIN')")
    public Object get(Authentication authentication) {
        //Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        authentication.getCredentials();
        OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();
        String token = details.getTokenValue();
        return token;
    }
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;



@EnableDiscoveryClient
@SpringBootApplication
public class DemoApplication {

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

}
package com.example.demo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

/**
 * Created by Administrator on 2020-06-13.
 */
@Configuration
@EnableResourceServer
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class ResourceServerConfig  extends ResourceServerConfigurerAdapter {
    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter accessTokenConverter = new JwtAccessTokenConverter();

        accessTokenConverter.setSigningKey("dev");
        accessTokenConverter.setVerifierKey("dev");
        return accessTokenConverter;
    }

    @Autowired
    private TokenStore jwtTokenStore;

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(jwtTokenStore);
    }

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers("/login").permitAll();
    }
}



    
    古时的风筝-OAuth2 Client






spring.application.name=client-authcode
server.port=6102
server.servlet.context-path=/client-authcode


spring.redis.database=2
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.jedis.pool.max-active=8
spring.redis.jedis.pool.max-idle=8
spring.redis.jedis.pool.min-idle=0
spring.redis.timeout=100ms



security.oauth2.client.client-id=code-client
security.oauth2.client.client-secret=code-secret-8888
security.oauth2.client.user-authorization-uri=http://localhost:6001/oauth/authorize
security.oauth2.client.access-token-uri=http://localhost:6001/oauth/token

#security.oauth2.resource.id=user-client
#security.oauth2.resource.user-info-uri=user-info

security.oauth2.resource.jwt.key-uri=http://localhost:6001/oauth/token_key
security.oauth2.resource.jwt.key-value=dev
security.oauth2.authorization.check-token-access=http://localhost:6001/oauth/check_token

eureka.client.service-url.defaultZone=http://peer1:1111/eureka/,http://peer2:1112/eureka/


	4.0.0
	
		org.springframework.boot
		spring-boot-starter-parent
		2.0.0.RELEASE
		 
	
	com.example
	demo
	0.0.1-SNAPSHOT
	demo
	Demo project for Spring Boot

	
		1.8
	


	
		
			org.springframework.boot
			spring-boot-starter-web
		

		
			org.springframework.boot
			spring-boot-starter-test
			test
			
				
					org.junit.vintage
					junit-vintage-engine
				
			
		

		
		
			org.springframework.cloud
			spring-cloud-starter-netflix-eureka-client
		


		
		
		
		

		
			org.springframework.cloud
			spring-cloud-starter-oauth2
		
		
			org.springframework.boot
			spring-boot-starter-data-redis
		
		
			io.jsonwebtoken
			jjwt
			0.9.1
		
		
			com.squareup.okhttp3
			okhttp
			3.14.2
		
		
			org.springframework.boot
			spring-boot-starter-thymeleaf
		

	


	
	
		
			
				org.springframework.cloud
				spring-cloud-dependencies
				Finchley.RELEASE
				pom
				import
			
		
	



	
		
			
				org.springframework.boot
				spring-boot-maven-plugin
			
		
	

完成以上步骤便代码已结束,需要启动两个注册中心,认证服务,第三方服务。

1.在浏览器中输入http://localhost:6001/oauth/authorize?client_id=code-client&response_type=code&redirect_uri=http://localhost:6102/client-authcode/login会出现以下页面,输入用户名admin 密码:123456(用户名密码是在程序中设定的)

Spring Cloud系列 Spring Cloud OAuth2授权码模式(authorization code)_第6张图片

2.输入用户名密码以后会跳转到授权页面,需要用户授权

Spring Cloud系列 Spring Cloud OAuth2授权码模式(authorization code)_第7张图片

3.点击授权以后,授权服务器会跳转到第三方应用,并传入code参数。第三方应用通过code参数获取token

你可能感兴趣的:(Spring Cloud系列 Spring Cloud OAuth2授权码模式(authorization code))