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

2021年4月25日 8点热度 0条评论 来源: wflovejava

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

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

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

1. oauth-server模块代码

1.1 添加基础pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>springcloud-oauth2</groupId>
    <artifactId>springcloud-oauth2</artifactId>
    <packaging>pom</packaging>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>oauth-server</module>
        <module>api-gateway</module>
        <module>eureka</module>
        <module>provide-service</module>
    </modules>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.3.RELEASE</version>
    </parent>
    <properties>
        <java.version>1.8</java.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <argLine>-Dfile.encoding=UTF-8</argLine>
        <skiptests>true</skiptests>
    </properties>
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <!--<version>Dalston.SR4</version>-->
                <version>Finchley.SR2</version>

                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <!--eureka客户端-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!--aop-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

        <!--监控-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <!--断路器-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-hystrix</artifactId>
        </dependency>

        <!--feign-->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <!--<dependency>-->
            <!--<groupId>org.glassfish.jersey.core</groupId>-->
            <!--<artifactId>jersey-common</artifactId>-->
            <!--<version>2.26</version>-->
        <!--</dependency>-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.20</version>
        </dependency>
        <!--日志动态级别修改-->
        <dependency>
            <groupId>org.jolokia</groupId>
            <artifactId>jolokia-core</artifactId>
        </dependency>
    </dependencies>
</project>

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

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>springcloud-oauth2</artifactId>
        <groupId>springcloud-oauth2</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>oauth-server</artifactId>

    <dependencies>
        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>2.9.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-data</artifactId>
        </dependency>


        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <!-- 1.5的版本默认采用的连接池技术是jedis  2.0以上版本默认连接池是lettuce, 在这里采用jedis,所以需要排除lettuce的jar -->
            <exclusions>
                <exclusion>
                    <groupId>redis.clients</groupId>
                    <artifactId>jedis</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>io.lettuce</groupId>
                    <artifactId>lettuce-core</artifactId>
                </exclusion>
            </exclusions>

        </dependency>
    </dependencies>
</project>

代码目录:

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<? extends GrantedAuthority> 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配置文件

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
    </dependencies>

3. api-gataway

3.1 添加pom文件

<dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
        </dependency>
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.retry</groupId>
            <artifactId>spring-retry</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
    </dependencies>

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

2.携带token访问provide-server服务

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<NameValuePair> values = new ArrayList<NameValuePair>();
        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接口,

然后在访问test1,出现需要授权访问401

最后我们携带token访问test1接口,访问成功

最后:模拟用户登录

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

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

    原文作者:wflovejava
    原文地址: https://blog.csdn.net/wflovejava/article/details/88558149
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系管理员进行删除。