欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-06-13 15:46:52
...

环境:

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);

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

确定此次开发依靠版本:

<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抽出公共工具类用于应用服务调用;其结构如下:

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

依赖包如下:

<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);
    }
}

3、授权服务(oauth2-server)

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

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

  • 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);
    }
}

4、网关服务(oauth2-gateway)

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

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

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

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);
    }
}

5、应用服务(oauth2-client)

结构如下:

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

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

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();
    }

}

6、测试

1、先后启动服务:

注册中心(eureka-server),

授权服务(oauth2-server),

网关服务(oauth2-gateway),

应用服务(auth2-client)

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

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

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

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

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

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

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

返回:

{
    "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

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

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

7、源码链接

https://github.com/cc6688211/oauth2