Spring Cloud下基于OAUTH2认证授权的实现

Spring Cloud下基于OAUTH2认证授权的实现

使用oauth2实现微服统一认证授权。通过向oauth2认证服务器发送请求获取token。
,然后携带token访问其他微服务,此token在其他微服务是信任的(即是鉴权验证token是否可用)

模块:
eureka:服务注册和发现的基本模块
zuul:边界网关(所有微服务都在它之后)
oauth2: OAUTH2认证授权中心
service :普通微服务,用来验证认证和鉴权

Spring Cloud下基于OAUTH2认证授权的实现_第1张图片

1. oauth-server模块代码

1.1 添加基础pom.xml文件:



    4.0.0

    springcloud-oauth2
    springcloud-oauth2
    pom
    1.0-SNAPSHOT
    
        oauth-server
        api-gateway
        eureka
        provide-service
    

    
        org.springframework.boot
        spring-boot-starter-parent
        2.0.3.RELEASE
    
    
        1.8
        UTF-8
        UTF-8
        -Dfile.encoding=UTF-8
        true
    
    
        
            
                org.springframework.cloud
                spring-cloud-dependencies
                
                Finchley.SR2

                pom
                import
            
        
    
    
        
            org.springframework.boot
            spring-boot-starter-web
        
        
            org.springframework.boot
            spring-boot-starter-test
            test
        

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

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

        
        
            org.springframework.cloud
            spring-cloud-starter-netflix-hystrix
        

        
        
            org.springframework.cloud
            spring-cloud-starter-openfeign
        

        
            
            
            
        
        
            org.projectlombok
            lombok
            1.16.20
        
        
        
            org.jolokia
            jolokia-core
        
    

1.2 添加oauth-server的pom.xml文件



    
        springcloud-oauth2
        springcloud-oauth2
        1.0-SNAPSHOT
    
    4.0.0

    oauth-server

    
        
            redis.clients
            jedis
            2.9.0
        
        
            com.google.code.gson
            gson
            2.8.2
        

        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            1.3.1
        

        
            org.springframework.cloud
            spring-cloud-starter-security
        

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

        
            org.springframework.security
            spring-security-data
        


        
            mysql
            mysql-connector-java
        

        
            org.springframework.boot
            spring-boot-starter-data-redis
            
            
                
                    redis.clients
                    jedis
                
                
                    io.lettuce
                    lettuce-core
                
            

        
    

代码目录:
Spring Cloud下基于OAUTH2认证授权的实现_第2张图片
1.3 添加认证的配置类,使用Redis用来存储token

@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private RedisConnectionFactory connectionFactory;

    @Autowired
    private UserDetailsService userDetailsService;
    @Bean
    public MyRedisTokenStore tokenStore() {
        return new MyRedisTokenStore(connectionFactory);
    }


    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                .authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)//若无,refresh_token会有UserDetailsService is required错误
                .tokenStore(tokenStore());
    }

    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }

    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("wf")
                .scopes("wf")
                .secret("wf")
                .authorizedGrantTypes("password", "authorization_code", "refresh_token");
    }

Resource服务配置类
auth-server提供user信息,所以auth-server也是一个Resource Server

/**
 * Created by wf on 2019/03/15
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .exceptionHandling()
                .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
            .and()
                .authorizeRequests()
                .anyRequest().authenticated()
            .and()
                .httpBasic();

    }
}

/**
 * @author wufei
 * @create 2019-03-13 16:31
 **/
@RestController
public class UserController {
    @GetMapping("/user")
    public Principal user(Principal user){
        return user;
    }
}

1.4 添加安全配置类

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {



    @Bean
    public UserDetailsService userDetailsService(){
        return new DomainUserDetailsService();
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return NoOpPasswordEncoder.getInstance();//new BCryptPasswordEncoder();
    }


    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService())
                .passwordEncoder(passwordEncoder());
    }

    @Bean
    public SecurityEvaluationContextExtension securityEvaluationContextExtension() {
        return new SecurityEvaluationContextExtension();
    }

    //不定义没有password grant_type
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

1.5 直接使用RedisTokenStore存储token会出现NoSuchMethodError RedisConnection.set([B[B)V错误

解决方案:自己编写一个MyRedisTokenStore,复制RedisTokenStore类中代码,并将代码中conn.set(accessKey,
serializedAccessToken)修改为conn.stringCommands().set(accessKey,
serializedAccessToken);

1.6 添加操作db类

/**
 * @author wufei
 * @create 2019-03-14 9:15
 **/
@Data
public class User {
    private int id;
    private String username;
    private String password;

}
@Mapper
public interface UserMapper {
    @Select("SELECT * FROM user WHERE username = #{username}")
    User findByUserName(@Param("username") String username);
}


@Service("userDetailsService")
@Slf4j
public class DomainUserDetailsService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
         User user =  userMapper.findByUserName(username);
        List authorities = new ArrayList();
        return  new org.springframework.security.core.userdetails.User(user.getUsername(),user.getPassword(),authorities);
    }
}

1.7 添加OauthServerApplication启动类

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.wf.oauth.db")
public class OauthServerApplication implements CommandLineRunner {

    private final UserMapper userMapper;

    public OauthServerApplication(UserMapper userMapper) {
        this.userMapper = userMapper;
    }


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

    @Override
    public void run(String... args) {
        User user = this.userMapper.findByUserName("admin");
        if(user != null){
            System.out.println("================: "+user.getUsername());
        }
    }
}

1.8 配置文件:

server:
  port: 8082
  tomcat:
    accept-count: 1000
    max-threads: 1000
    max-connections: 2000
spring:
  application:
      name: auth-server
  datasource:
      url: jdbc:mysql://192.168.3.1:3306/test?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true
      username: xx
      driver-class-name: com.mysql.jdbc.Driver
      password: xx
  redis:
    host: 192.168.3.1
    port: 6380
    password: xinleju
    database: 15
    jedis:
      pool:
        max-active: 8
        max-wait: -1ms
        max-idle: 8
        min-idle: 0

logging:
  level:
    org:
      springframework:
        security: DEBUG
wf:
  oauth:
    clientid: wf_oauth
    secret: wf_secret
eureka:
  client:
    service-url:
      defaultZone: http://localhost:1111/eureka/
  instance:
    prefer-ip-address: true #显示服务器IP

2. provide-server模块代码

provide-service是一个简单的微服务,使用auth-server进行认证授权,在它的配置文件指定用户信息在auth-server的地址即可:

info:
  version: "v1"
  name: "provide"
management:
  endpoints:
    web:
      exposure:
        include: '*'
#端口号
server:
   port: 8081
   tomcat:
     max-threads: 200
spring:
  application:
    name: provide-server
eureka:
  client:
    service-url:
      defaultZone: http://localhost:1111/eureka/
  instance:
    prefer-ip-address: true #显示服务器IP
security:
  oauth2:
    resource:
      id: service
      user-info-uri: http://localhost:9999/uaa/user # 使用auth-server进行认证授权,
      prefer-token-info: false

2.1 配置资源服务

@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {

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

    @Override
    public void configure(HttpSecurity http) throws Exception {
                http.authorizeRequests().antMatchers("/test/test2").permitAll().and() //不需要授权认证                    .csrf().disable()
                .exceptionHandling()
                .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
                .and()
                .authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .httpBasic();
    }
}
@RestController
@RequestMapping("test")
public class UserController {

    @RequestMapping("test1")
    public  Object test(){
        return "ok";
    }
}

2.2 ProvideApplication启动类:

@SpringCloudApplication
public class ProvideApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProvideApplication.class,args);
        System.out.println("ProvideApplication 启动Ok!");
    }
}

2.3 添加pom配置文件


        
            org.springframework.cloud
            spring-cloud-starter-security
        

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

3. api-gataway

3.1 添加pom文件


        
            org.springframework.cloud
            spring-cloud-starter-netflix-zuul
        
        
            com.squareup.okhttp3
            okhttp
        

        
            org.springframework.retry
            spring-retry
        
        
            org.springframework.cloud
            spring-cloud-starter-security
        

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

3.2 关闭csrf并开启Oauth2 client支持

/**
 * 关闭csrf跨站请求伪造并开启Oauth2 client支持
 * @author wufei
 * @create 2019-03-15 9:16
 **/
@Configuration
@EnableOAuth2Sso
public class SecurityConfig  extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable();
    }

}

3.3 配置文件

info:
  version: "v1"
  name: "zuul"
management:
  endpoints:
    web:
      exposure:
        include: '*'
#端口号
server:
   port: 9999
   tomcat:
     max-threads: 200
spring:
  application:
    name: zuul
eureka:
  client:
    service-url:
      defaultZone: http://localhost:1111/eureka/
  instance:
    prefer-ip-address: true #显示服务器IP
zuul:
  routes:
    uaa:
      sensitiveHeaders:
      serviceId: auth-server
    provide:
      sensitiveHeaders:
      serviceId: provide-server
  add-proxy-headers: true

security:
  oauth2:
    client:
      access-token-uri: http://localhost:9999/uaa/oauth/token #网关的地址
      user-authorization-uri: http://localhost:9999/uaa/oauth/authorize
      client-id: wf
    resource:
      user-info-uri: http://localhost:9999/uaa/user
      prefer-token-info: false

3.4添加启动类ApiGateWayApplication
开启支持Sso

@SpringBootApplication
@EnableZuulProxy
@EnableDiscoveryClient
@EnableOAuth2Sso
public class ApiGateWayApplication {
    public static void main(String[] args) {
        SpringApplication.run(ApiGateWayApplication.class, args);
    }

}

4. 演示

分别启动eureka、zuul、auth-server、provide服务
4.1 客户端Postman调用
使用Postman通过网关http://localhost:9999/uaa/oauth/token发送请求获得access_token,用户名和密码分别是admin、admin,授权方式为password
Spring Cloud下基于OAUTH2认证授权的实现_第3张图片
Spring Cloud下基于OAUTH2认证授权的实现_第4张图片

2.携带token访问provide-server服务
Spring Cloud下基于OAUTH2认证授权的实现_第5张图片

4.2 客户端httpclient调用

/**
 * @author wufei
 * @create 2019-03-14 14:38
 **/
public class TokenTest {
    private static HttpClientContext context = HttpClientContext.create();

    public static void main(String[] args) {

        String loginUrl = "http://localhost:9999/uaa/oauth/token";

        String username  = "admin";
        String password = "admin";

        String scope = "wf";

        String clientId = "wf";

        String clientSecret = "wf";

        CloseableHttpClient httpClient = HttpClientPool.getHttpClient();//HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build();
        HttpPost httpPost = new HttpPost(loginUrl);
        List values = new ArrayList();
        values.add(new BasicNameValuePair("grant_type", "password"));
        values.add(new BasicNameValuePair("username", username));
        values.add(new BasicNameValuePair("password", password));
        values.add(new BasicNameValuePair("scope", scope));
        UrlEncodedFormEntity entity = new UrlEncodedFormEntity(values, Consts.UTF_8);
        httpPost.setEntity(entity);
        CloseableHttpResponse response = null;
        try {
            httpPost.setHeader("authorization", "Basic " + Base64.getEncoder().encodeToString((clientId + ":" + clientSecret).getBytes("utf-8")));
            String body = "";
            response = httpClient.execute(httpPost, context);
            HttpEntity httpEntity = response.getEntity();
            if (httpEntity != null) {
                try {
                    body = EntityUtils.toString(httpEntity, Consts.UTF_8);
                    System.out.println("=======================body: "+body);
                    EntityUtils.consume(httpEntity);
                } catch (IOException e) {

                }
            }
        } catch (IOException e) {

        } finally {
            try {
                response.close();
            } catch (Exception e) {

            }
        }
    }
}

返回结果:

=======================body: {"access_token":"277ab3a0-f415-4c03-b48b-1e590324afef","token_type":"bearer","refresh_token":"63243753-5ed3-421c-bf02-096f65bc8c9c","expires_in":36738,"scope":"wf"}

4.3 如果有些接口不需要oauth2认证呢?

 解决方案:在ResourceServerConfig配置上添加如下:
        http.authorizeRequests().antMatchers("/test/test2").permitAll().and() //不需要授权认证

现在我们provide-service服务有两个接口test1和test2,test2设置了不需要验证,
现在我们直接访问test2接口,
Spring Cloud下基于OAUTH2认证授权的实现_第6张图片
然后在访问test1,出现需要授权访问401
Spring Cloud下基于OAUTH2认证授权的实现_第7张图片
最后我们携带token访问test1接口,访问成功
Spring Cloud下基于OAUTH2认证授权的实现_第8张图片
最后:模拟用户登录

@RequestMapping("/login")
public String login(){
    //1验证用户和密码是否正确
    //2.清空原来的用户token
    //3.请求认证信息,颁发token
    //4.返回用户信息和token信息
    return null;
}

git源码地址:https://github.com/wflovejava/springcloud-oauth2.git

你可能感兴趣的:(spring,cloud)