Spring Cloud Oauth2实现分布式权限认证(redis版)

目录

环境:

摘要说明:

步骤:

一、整体结构及配置

二、公共模块(oauth2-common)

三、授权服务(oauth2-server)

四、网关服务(oauth2-gateway)

五、应用服务(oauth2-client)

六、测试

七、源码链接

环境:

JDK1.8,spring-boot(2.0.3.RELEASE),spring cloud(Finchley.RELEASE)

摘要说明:

微服务应用架构下,权限认证是一个不可避免的问题,此时OAuth2就是一个很好的选择;

OAuth(Open Authorization,开放授权)是为用户资源的授权定义了一个安全、开放及简单的标准,第三方无需知道用户的账号及密码,就可获取到用户的授权信息
OAuth2.0是OAuth协议的延续版本,但不向后兼容OAuth 1.0即完全废止了OAuth1;

本篇文章主要讲述用授权服务(oauth2-server)、网关服务(oauth2-gateway)、应用服务(oauth2-client)实战模拟分布式权限认证;

实际步骤如下:

1、用户通过用户名密码通过网关服务(oauth2-gateway)调用授权服务(oauth2-server)以密码模式(resource owner password credentials)进行认证授权。

2、授权服务(oauth2-server)授权成功生成token返回,并将授权信息和token保存在redis

3、后续用户进行接口调用时携带token通过网关服务(oauth2-gateway)调用应用服务(oauth2-client);应用服务会先对接口地址进行过滤拦截,通过token访问授权服务(oauth2-server)进行权限校验;

步骤:

一、整体结构及配置

代码采用聚合机构,服务主要有注册中心(eureka-server)、授权服务(oauth2-server)、网关服务(oauth2-gateway)、应用服务(oauth2-client)及公共模块(oauth2-common);

确定此次开发依靠版本:

<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>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.0.3.RELEASE</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>
	<groupId>cn.cc.study</groupId>
	<artifactId>oauth2</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>pom</packaging>
	<properties>
		<java.version>1.8</java.version>
		<spring-cloud.version>Finchley.RELEASE</spring-cloud.version>
	</properties>
	<modules>
		<module>oauth2-server</module>
		<module>eureka-server</module>
		<module>oauth2-gateway</module>
		<module>oauth2-client</module>
		<module>oauth2-common</module>
	</modules>
	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.cloud</groupId>
				<artifactId>spring-cloud-dependencies</artifactId>
				<version>${spring-cloud.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>
	<dependencies>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<version>1.18.10</version>
		</dependency>
	</dependencies>
	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

二、公共模块(oauth2-common)

授权服务(oauth2-server)、应用服务(oauth2-client)包含公共模块(oauth2-common);

oauth2-common抽出公共工具类用于应用服务调用;其结构如下:

依赖包如下:

<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>
	<parent>
		<groupId>cn.cc.study</groupId>
		<artifactId>oauth2</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<artifactId>oauth2-common</artifactId>
	<dependencies>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-oauth2</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>fastjson</artifactId>
			<version>1.2.30</version>
		</dependency>
	</dependencies>
</project>

其中CurrentUser用于封装用户信息:

package cn.cc.study.common.dto;

import java.util.List;
import java.util.Map;
import java.util.Set;

import lombok.Data;

/**
 * 
 * @模块名:oauth2-common
 * @包名:cn.cc.study.common.dto
 * @类名称: CurrentUser
 * @类描述:【类描述】用户封装类
 * @版本:1.0
 * @创建人:cc
 * @创建时间:2019年11月19日下午1:39:35
 */
@Data
public class CurrentUser {
    private static final long serialVersionUID = 1L;

    /**
     * 客户端id
     */
    private String clientId;

    /**
     * 用户id
     */
    private Integer userId;

    /**
     * 角色列表
     */
    private List < Integer > roleIds;

    /**
     * 用户属性
     */
    private Map < String, Object > params;

    private String password;

    /**
     * 用户名称
     */
    private String username;

    /**
     * 用户权限
     */
    private Set < Authority > authorities;

    private boolean accountNonExpired;

    private boolean accountNonLocked;

    private boolean credentialsNonExpired;

    private boolean enabled;

    public CurrentUser() {
        super();
    }

    @Data
    public static class Authority {
        public String authority;

        Authority() {
            super();
        }
    }
}

SecurityUtil是用于服务进行当前用户的获取:

package com.tit.cmsp.authorization.auth.client.util;

import java.util.List;
import java.util.Set;

import org.springframework.security.core.context.SecurityContextHolder;

import com.alibaba.fastjson.JSON;
import com.tit.cmsp.authorization.auth.client.api.CurrentUser;
import com.tit.cmsp.authorization.auth.client.api.CurrentUser.Authority;

/**
 * 
 * @模块名:cmsp-authorization-common
 * @包名:com.tit.cmsp.authorization.common.util
 * @类名称: SecurityUtil
 * @类描述:【类描述】Security工具类
 * @版本:1.0
 * @创建人:cc
 * @创建时间:2019年9月9日下午5:26:47
 */
public class SecurityUtil {
    /**
     * 
     * @方法名:currentUser
     * @方法描述【方法功能描述】获取当前用户信息
     * @return
     * @修改描述【修改描述】
     * @版本:1.0
     * @创建人:cc
     * @创建时间:2019年9月9日 下午5:27:13
     * @修改人:cc
     * @修改时间:2019年9月9日 下午5:27:13
     */
    public static CurrentUser currentUser() {
        CurrentUser currentUser = JSON.parseObject(
                JSON.parseObject(JSON.toJSONString(SecurityContextHolder.getContext().getAuthentication()))
                        .getJSONObject("userAuthentication").getJSONObject("details").getJSONObject("principal")
                        .toJSONString(), CurrentUser.class);
        return currentUser;
    }

    /**
     * 
     * @方法名:getRoleIds
     * @方法描述【方法功能描述】获取当前角色id
     * @return
     * @修改描述【修改描述】
     * @版本:1.0
     * @创建人:cc
     * @创建时间:2019年9月9日 下午5:28:21
     * @修改人:cc
     * @修改时间:2019年9月9日 下午5:28:21
     */
    public static List < Integer > getRoleIds() {
        return currentUser().getRoleIds();
    }

    /**
     * 
     * @方法名:getRoleIds
     * @方法描述【方法功能描述】获取权限集合
     * @return
     * @修改描述【修改描述】
     * @版本:1.0
     * @创建人:cc
     * @创建时间:2019年9月9日 下午5:28:58
     * @修改人:cc
     * @修改时间:2019年9月9日 下午5:28:58
     */
    public static Set < Authority > getGrantedAuthoritys() {
        return currentUser().getAuthorities();
    }

    /**
     * 
     * @方法名:isGrantedAuthority
     * @方法描述【方法功能描述】是否含有该权限
     * @param menu
     * @return
     * @修改描述【修改描述】
     * @版本:1.0
     * @创建人:cc
     * @创建时间:2019年9月9日 下午5:30:49
     * @修改人:cc
     * @修改时间:2019年9月9日 下午5:30:49
     */
    public static boolean isGrantedAuthority(String menu) {
        return currentUser().getAuthorities().contains(menu);
    }

    /**
     * 
     * @方法名:getParam
     * @方法描述【方法功能描述】获取用户属性
     * @param key
     * @return
     * @修改描述【修改描述】
     * @版本:1.0
     * @创建人:cc
     * @创建时间:2019年9月9日 下午5:32:08
     * @修改人:cc
     * @修改时间:2019年9月9日 下午5:32:08
     */
    public static Object getParam(String key) {
        return currentUser().getParams().get(key);
    }
}

三、授权服务(oauth2-server)

授权服务(oauth2-server)目录结构如下:

  • MssWebResponseExceptionTranslator:异常翻译
  • AuthorizationServerConfiguration:授权服务配置
  • ResourceServerConfig:资源服务配置
  • NoEncryptPasswordEncoder:自定义加密策略
  • SecurityConfig:认证策略配置
  • RedisTokenStore:重写RedisTokenStore
  • MemberController:测试控制类
  • pojo包:用于构建权限模型
  • MyUserDetailService:重新UserDetailService,用于构建用户及授权信息
  • DigestUtil:加密工具类
  • Oauth2ServerApplication:启动类配置

下面会选择关键代码贴出,多余的见后面的源码;

服务依赖如下:

<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>
	<parent>
		<groupId>cn.cc.study</groupId>
		<artifactId>oauth2</artifactId>
		<version>0.0.1-SNAPSHOT</version>
	</parent>
	<artifactId>oauth2-server</artifactId>
	<dependencies>
		<dependency>
			<groupId>cn.cc.study</groupId>
			<artifactId>oauth2-common</artifactId>
			<version>0.0.1-SNAPSHOT</version>
		</dependency>
		<dependency>
			<groupId>org.springframework.cloud</groupId>
			<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-actuator</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>

服务配置如下:

spring:
  application:
    name: oauth2-server
  redis:
    host: 127.0.0.1
    database: 0
server:
  port: 9098
eureka:
  instance:
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/

AuthorizationServerConfiguration(授权服务配置):


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
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.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;

import cn.cc.study.config.RedisTokenStore;
import cn.cc.study.config.error.MssWebResponseExceptionTranslator;
import cn.cc.study.service.MyUserDetailService;
import cn.cc.study.util.DigestUtil;

/**
 * @模块名:oauth2-server
 * @包名:cn.cc.study.config
 * @类名称: AuthorizationServerConfiguration
 * @类描述:【类描述】授权服务配置
 * @版本:1.0
 * @创建人:cc
 * @创建时间:2019年11月18日上午11:16:27
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    /**
     * 认证管理器
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisConnectionFactory redisConnectionFactory;

    /**
     * 用户认证器
     */
    @Autowired
    private MyUserDetailService userDetailsService;

    /**
     * 
     * @方法名:tokenStore
     * @方法描述:自定义储存策略
     * @return
     * @修改描述:
     * @版本:1.0
     * @创建人:cc
     * @创建时间:2019年11月19日 下午1:56:02
     * @修改人:cc
     * @修改时间:2019年11月19日 下午1:56:02
     */
    @Bean
    public TokenStore tokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }

    /**
     * 定义令牌端点上的安全性约 束
     * 
     * (non-Javadoc)
     * 
     * @see org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter#configure(org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer)
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients().tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }

    /**
     * 用于定义客户端详细信息服务的配置程序。可以初始化客户端详细信息;
     * 
     * (non-Javadoc)
     * 
     * @see org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter#configure(org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer)
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        // clients.withClientDetails(clientDetails());
        clients.inMemory().withClient("android").scopes("read").secret(DigestUtil.encrypt("android"))
                .authorizedGrantTypes("password", "authorization_code", "refresh_token").and().withClient("webapp")
                .scopes("read").authorizedGrantTypes("implicit").and().withClient("browser")
                .authorizedGrantTypes("refresh_token", "password").scopes("read");
    }

    @Bean
    public WebResponseExceptionTranslator webResponseExceptionTranslator() {
        return new MssWebResponseExceptionTranslator();
    }

    /**
     * 定义授权和令牌端点以及令牌服务
     * 
     * (non-Javadoc)
     * 
     * @see org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter#configure(org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer)
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints
                // 指定认证管理器
                .authenticationManager(authenticationManager)
                // 用户账号密码认证
                .userDetailsService(userDetailsService)
                // refresh_token
                .reuseRefreshTokens(false)
                // 指定token存储位置
                .tokenStore(tokenStore()).tokenServices(defaultTokenServices());
    }

    /**
     * <p>
     * 注意,自定义TokenServices的时候,需要设置@Primary,否则报错,
     * </p>
     * 
     * @return
     */
    @Primary
    @Bean
    public DefaultTokenServices defaultTokenServices() {
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(tokenStore());
        tokenServices.setSupportRefreshToken(true);
        // tokenServices.setClientDetailsService(clientDetails());
        // token有效期自定义设置,默认12小时
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24 * 7);
        // tokenServices.setAccessTokenValiditySeconds(60 * 60 * 12);
        // refresh_token默认30天
        tokenServices.setAccessTokenValiditySeconds(60 * 60 * 24 * 7);
        // tokenServices.setRefreshTokenValiditySeconds(60 * 60 * 24 * 7);
        return tokenServices;
    }
}

ResourceServerConfig(资源服务配置):主要配置antMatchers,用于进行接口url拦截设置,此处拦截/api及其子路径;若全部拦截设置成/** 或 ** 。


import javax.servlet.http.HttpServletResponse;

import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
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;

/**
 * @模块名:oauth2-server
 * @包名:cn.cc.study.config
 * @类名称: ResourceServerConfig
 * @类描述:【类描述】定义资源管理配置,注意antMatchers的路径
 * @版本:1.0
 * @创建人:cc
 * @创建时间:2019年11月18日下午4:10:31
 */

@Configuration
@EnableResourceServer
@Order(3)
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().requestMatchers().antMatchers("/api/**").and().authorizeRequests().antMatchers("/api/**")
                .authenticated().and().httpBasic();
    }
}

SecurityConfig(认证配置):


import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.password.PasswordEncoder;

import cn.cc.study.service.MyUserDetailService;

/**
 * @模块名:oauth2-server
 * @包名:cn.cc.study.config
 * @类名称: SecurityConfig
 * @类描述:【类描述】Security配置
 * @版本:1.0
 * @创建人:cc
 * @创建时间:2019年11月18日下午4:11:12
 */

@Configuration
@EnableWebSecurity
@Order(2)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private MyUserDetailService userDetailService;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new NoEncryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.requestMatchers().antMatchers("/oauth/**").and().authorizeRequests().antMatchers("/oauth/**")
                .authenticated().and().csrf().disable();
    }

    /**
     * 实现认证策略
     * 
     * (non-Javadoc)
     * 
     * @see org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter#configure(org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder)
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
    }

    /**
     * 不定义没有password grant_type,密码模式需要AuthenticationManager支持
     *
     * @return
     * @throws Exception
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

MyUserDetailService:自定义用户授权信息,这里面只授权hello权限用户测试:


import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

import cn.cc.study.pojo.BaseUser;
import cn.cc.study.pojo.Menu;
import cn.cc.study.pojo.Role;
import cn.cc.study.pojo.User;
import cn.cc.study.util.DigestUtil;

/**
 * 
 * @模块名:oauth2-server
 * @包名:cn.cc.study.service
 * @类名称: MyUserDetailService
 * @类描述:【类描述】自定义UserDetailService
 * @版本:1.0
 * @创建人:cc
 * @创建时间:2019年11月18日下午2:31:11
 */
@Service("userDetailService")
public class MyUserDetailService implements UserDetailsService {

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User member = new User();
        member.setAccount("admin");
        try {
            member.setPassword(DigestUtil.encrypt("123456"));
        }
        catch (Exception e) {
            System.out.println("加密错误");
        }
        if (member == null) {
            throw new UsernameNotFoundException(username);
        }
        Set < GrantedAuthority > grantedAuthorities = new HashSet <>();
        // 可用性 :true:可用 false:不可用
        boolean enabled = true;
        // 过期性 :true:没过期 false:过期
        boolean accountNonExpired = true;
        // 有效性 :true:凭证有效 false:凭证无效
        boolean credentialsNonExpired = true;
        // 锁定性 :true:未锁定 false:已锁定
        boolean accountNonLocked = true;
        List < Role > roles = new ArrayList <>();
        Role role1 = new Role();
        role1.setId(1);
        role1.setRoleName("admin");
        roles.add(role1);
        List < Integer > roleIds = new ArrayList < Integer >();
        for (Role role : roles) {
            // 角色必须是ROLE_开头,可以在数据库中设置
            GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getRoleName());
            grantedAuthorities.add(grantedAuthority);
            roleIds.add(role.getId());
            // 获取权限
            List < Menu > menus = new ArrayList <>();
            Menu menu1 = new Menu();
            menu1.setId(1);
            menu1.setCode("hello");
            for (Menu menu : menus) {
                GrantedAuthority authority = new SimpleGrantedAuthority(menu.getCode());
                grantedAuthorities.add(authority);
            }
        }
        BaseUser user = new BaseUser(member.getAccount(), member.getPassword(), enabled, accountNonExpired,
                credentialsNonExpired, accountNonLocked, grantedAuthorities);
        user.setClientId("test");
        user.setUserId(member.getId());
        user.setRoleIds(roleIds);
        Map < String, Object > params = new HashMap < String, Object >();
        params.put("aa", "aa");
        user.setParams(params);
        return user;

    }
}

 NumberController如下:


import java.security.Principal;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.oauth2.provider.token.ConsumerTokenServices;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 
 * @模块名:oauth2-server
 * @包名:cn.cc.study.controller
 * @类名称: MemberController
 * @类描述:【类描述】权限认证控制层
 * @版本:1.0
 * @创建人:cc
 * @创建时间:2019年11月18日下午4:34:55
 */
@RestController
@RequestMapping("/api")
public class MemberController {

    @Autowired
    private ConsumerTokenServices consumerTokenServices;

    @GetMapping("hello")
    @PreAuthorize("hasAnyAuthority('hello')")
    public String hello() {
        return "hello";
    }

    @GetMapping("query")
    @PreAuthorize("hasAnyAuthority('query')")
    public String query() {
        return "query";
    }

    /**
     * 
     * @方法名:user
     * @方法描述:用于进行权限校验查询
     * @param member
     * @return
     * @修改描述:
     * @版本:1.0
     * @创建人:cc
     * @创建时间:2019年11月19日 下午4:07:52
     * @修改人:cc
     * @修改时间:2019年11月19日 下午4:07:52
     */
    @GetMapping("/member")
    public Principal user(Principal member) {
        return member;
    }

    @DeleteMapping(value = "/exit")
    public boolean revokeToken(String access_token) {
        return consumerTokenServices.revokeToken(access_token);
    }
}

四、网关服务(oauth2-gateway)

网关服务(oauth2-gateway)目录结构如下:

配置如下,主要配置转发策略及进行授权接口设置:

server:
  port: 1202
spring:
  application:
    name: oauth2-gateway
eureka:
  instance:
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/
zuul:
  routes:
    auth:
      path: /auth/**
      serviceId: oauth2-server
      sensitiveHeaders: '*'
    client:
      path: /client/**
      serviceId: oauth2-client
      sensitiveHeaders: '*'
  retryable: false
  ignored-services: '*'
  ribbon:
    eager-load:
      enabled: true
  host:
    connect-timeout-millis: 3000
    socket-timeout-millis: 3000
  add-proxy-headers: true
security:
  oauth2:
    client:
      access-token-uri: http://localhost:${server.port}/auth/oauth/token
      user-authorization-uri: http://localhost:${server.port}/auth/oauth/authorize
      client-id: web
    resource:
      user-info-uri: http://localhost:${server.port}/auth/api/member
      prefer-token-info: false
ribbon:
  ReadTimeout: 3000
  ConnectTimeout: 3000
  MaxAutoRetries: 1
  MaxAutoRetriesNextServer: 2
  eureka:
    enabled: true
hystrix:
  command:
    default:
      execution:
        timeout:
          enabled: true
        isolation:
          thread:
            timeoutInMilliseconds: 3500

ZuulApplication配置如下:


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;

@SpringBootApplication
@EnableDiscoveryClient
/**
 * 将OAuth2访问令牌下游转发到它所代理的服务
 */
@EnableZuulProxy
/**
 * 客户端令牌中继
 */
@EnableOAuth2Sso
public class ZuulApplication {

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

五、应用服务(oauth2-client)

结构如下:

配置如下,主要说明进行权限认证的:

spring:
  application:
    name: oauth2-client
server:
  port: 9099
eureka:
  instance:
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
  client:
    service-url:
      defaultZone: http://localhost:7001/eureka/
security:
  oauth2:
    resource:
      id: oauth2-client
      user-info-uri: http://localhost:1202/auth/api/member#验证传入的令牌

ResourceServerConfig(资源认证配置):

@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().requestMatchers().antMatchers("/api/**").and().authorizeRequests().antMatchers("/api/**")
                .authenticated().and().httpBasic();
    }
}

TestController(测试类):

@RestController
@RequestMapping("/api")
public class TestController {
    /**
     * 
     * @方法名:hello
     * @方法描述:用于测试拥有hello的权限
     * @return
     * @修改描述:
     * @版本:1.0
     * @创建人:cc
     * @创建时间:2019年11月19日 下午1:48:06
     * @修改人:cc
     * @修改时间:2019年11月19日 下午1:48:06
     */
    @GetMapping("hello")
    @PreAuthorize("hasAnyAuthority('hello')")
    public String hello() {
        return "hello";
    }

    /**
     * 
     * @方法名:query
     * @方法描述:用户测试未含有query的权限
     * @return
     * @修改描述:
     * @版本:1.0
     * @创建人:cc
     * @创建时间:2019年11月19日 下午1:50:13
     * @修改人:cc
     * @修改时间:2019年11月19日 下午1:50:13
     */
    @GetMapping("query")
    @PreAuthorize("hasAnyAuthority('query')")
    public String query() {
        return "具有query权限";
    }

    /**
     * 
     * @方法名:test
     * @方法描述:未加权限则登录即可访问
     * @return
     * @修改描述:
     * @版本:1.0
     * @创建人:cc
     * @创建时间:2019年11月19日 下午1:48:44
     * @修改人:cc
     * @修改时间:2019年11月19日 下午1:48:44
     */
    @GetMapping("test")
    public String test() {
        return "test";
    }

    /**
     * 
     * @方法名:current
     * @方法描述:获取用户的方式1
     * @param principal
     * @return
     * @修改描述:
     * @版本:1.0
     * @创建人:cc
     * @创建时间:2019年11月19日 下午1:49:23
     * @修改人:cc
     * @修改时间:2019年11月19日 下午1:49:23
     */
    @GetMapping("current")
    public Principal current(Principal principal) {
        return principal;
    }

    /**
     * 
     * @方法名:current1
     * @方法描述:获取用户的方式2
     * @param authentication
     * @return
     * @修改描述:
     * @版本:1.0
     * @创建人:cc
     * @创建时间:2019年11月19日 下午1:49:54
     * @修改人:cc
     * @修改时间:2019年11月19日 下午1:49:54
     */
    @GetMapping("current1")
    public Authentication current1(Authentication authentication) {
        return authentication;
    }

    /**
     * 
     * @方法名:current2
     * @方法描述:获取用户的方式3
     * @return
     * @修改描述:
     * @版本:1.0
     * @创建人:cc
     * @创建时间:2019年11月19日 下午1:50:02
     * @修改人:cc
     * @修改时间:2019年11月19日 下午1:50:02
     */
    @GetMapping("current2")
    public CurrentUser current2() {
        return SecurityUtil.currentUser();
    }

}

六、测试

1、先后启动服务:

注册中心(eureka-server),

授权服务(oauth2-server),

网关服务(oauth2-gateway),

应用服务(auth2-client)

2、进行登录,输入授权类型,用户名,密码。并设置client的名称及密码,返回token:

3、调用oauth2-server当前用户接口(http://127.0.0.1:1202/auth/api/member),这里面携带token有两种方式,一种是header里添加Authorization:token_type+token,一种添加到参数中,access_token=token:

返回:

{
    "authorities": [
        {
            "authority": "admin"
        },
        {
            "authority": "hello"
        }
    ],
    "details": {
        "remoteAddress": "10.83.5.204",
        "sessionId": null,
        "tokenValue": "184113d3-f093-4e77-b927-25c74ae8bd54",
        "tokenType": "Bearer",
        "decodedDetails": null
    },
    "authenticated": true,
    "userAuthentication": {
        "authorities": [
            {
                "authority": "admin"
            },
            {
                "authority": "hello"
            }
        ],
        "details": {
            "grant_type": "password",
            "username": "admin"
        },
        "authenticated": true,
        "principal": {
            "password": null,
            "username": "admin",
            "authorities": [
                {
                    "authority": "admin"
                },
                {
                    "authority": "hello"
                }
            ],
            "accountNonExpired": true,
            "accountNonLocked": true,
            "credentialsNonExpired": true,
            "enabled": true,
            "clientId": "test",
            "userId": null,
            "roleIds": [
                1
            ],
            "params": {
                "aa": "aa"
            }
        },
        "credentials": null,
        "name": "admin"
    },
    "principal": {
        "password": null,
        "username": "admin",
        "authorities": [
            {
                "authority": "admin"
            },
            {
                "authority": "hello"
            }
        ],
        "accountNonExpired": true,
        "accountNonLocked": true,
        "credentialsNonExpired": true,
        "enabled": true,
        "clientId": "test",
        "userId": null,
        "roleIds": [
            1
        ],
        "params": {
            "aa": "aa"
        }
    },
    "credentials": "",
    "oauth2Request": {
        "clientId": "android",
        "scope": [
            "read"
        ],
        "requestParameters": {
            "grant_type": "password",
            "username": "admin"
        },
        "resourceIds": [],
        "authorities": [],
        "approved": true,
        "refresh": false,
        "redirectUri": null,
        "responseTypes": [],
        "extensions": {},
        "refreshTokenRequest": null,
        "grantType": "password"
    },
    "clientOnly": false,
    "name": "admin"
}

 4、测试接口权限,访问

http://127.0.0.1:1202/client/api/hello?access_token=184113d3-f093-4e77-b927-25c74ae8bd54

http://127.0.0.1:1202/client/api/query?access_token=184113d3-f093-4e77-b927-25c74ae8bd54

七、源码链接

https://github.com/cc6688211/oauth2

  • 1
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值