持续创作,加快生长!这是我参与「日新方案 10 月更文挑战」的第31天,点击检查活动详情

认证授权中心

添加依靠

	 <!-- spring web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- spring data redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <!-- mybatis -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!-- hutool -->
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!-- spring cloud security -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-core</artifactId>
        </dependency>
        <!-- spring cloud oauth2 -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
      <dependencyManagement>
        <dependencies>
            <!-- spring cloud 依靠 -->
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>Hoxton.SR8</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

application.yml装备

server:
  port: 8888 # 端口
spring:
  application:
    name: oauth2-server # 运用名
  # 数据库
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: root
    password: 123456
    url: jdbc:mysql://127.0.0.1:3306/demo?serverTimezone=Asia/Shanghai&characterEncoding=utf8&useUnicode=true&useSSL=false
  # Redis
  redis:
    port: 6379
    host: 127.0.0.1
    timeout: 3000
    database: 1
    password:
# Oauth2
client:
  oauth2:
    client-id: appId # 客户端标识 ID
    secret: 123456 # 客户端安全码
    # 授权类型
    grant_types:
      - password
      - refresh_token
    # token 有用时间,单位秒
    token-validity-time: 2592000
    refresh-token-validity-time: 2592000
    # 客户端拜访规模
    scopes:
      - api
      - all
# Mybatis
mybatis:
  configuration:
    map-underscore-to-camel-case: true # 开启驼峰映射

Security装备

装备运用Redis存储Token信息
装备暗码的加密、解密、校验逻辑 
初始化认证办理目标   
装备恳求拜访的放行和认证规矩
import cn.hutool.crypto.digest.DigestUtil;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import javax.annotation.Resource;
/**
 * Security装备
 */
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
    /**
     * 注入Redis衔接工厂
     */
    @Resource
    private RedisConnectionFactory redisConnectionFactory;
    /**
     * 初始化RedisTokenStore,用于将token存储至Redis
     *
     * @return
     */
    @Bean
    public RedisTokenStore redisTokenStore() {
        RedisTokenStore redisTokenStore = new RedisTokenStore(redisConnectionFactory);
        // 设置key的层级前缀
        redisTokenStore.setPrefix("TOKEN:");
        return redisTokenStore;
    }
    /**
     * 初始化暗码编码器,指定编码与校验规矩,用MD5加密暗码
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        // Security官方推荐的BCryptPasswordEncoder加密与校验类
        // 密钥的迭代次数(默许为10)
        //return new BCryptPasswordEncoder(10);
        return new PasswordEncoder() {
            /**
             * 加密
             * @param rawPassword 原始暗码
             * @return
             */
            @Override
            public String encode(CharSequence rawPassword) {
                return DigestUtil.md5Hex(rawPassword.toString());
            }
            /**
             * 校验暗码
             * @param rawPassword       原始暗码
             * @param encodedPassword   加密暗码
             * @return
             */
            @Override
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return DigestUtil.md5Hex(rawPassword.toString()).equals(encodedPassword);
            }
        };
    }
    /**
     * 初始化认证办理目标
     *
     * @return
     * @throws Exception
     */
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    /**
     * 放行和认证规矩
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 禁用csrf
        http.csrf().disable()
                .authorizeRequests()
                // 放行的恳求
                .antMatchers("/oauth/**", "/actuator/**").permitAll()
                .and()
                .authorizeRequests()
                // 其他恳求必须认证才干拜访
                .anyRequest().authenticated();
    }
}

登录认证装备

创立UserService类完成UserDetailsService类重写loadUserByUsername办法,该办法首要完成登录、认证校验逻辑,这里简略模仿。

public interface UserMapper {
    /**
     * 根据用户名 or 手机号 or 邮箱查询用户信息
     * @param account
     * @return
     */
    @Select("select id, username, phone, email, password, roles from user where " +
            "(username = #{account} or phone = #{account} or email = #{account})")
    Diners selectByAccountInfo(@Param("account") String account);
}
@Service
public class UserService implements UserDetailsService {
    @Resource
    private UserMapper userMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (StrUtil.hasBlank(username)) {
            throw new RuntimeException("用户名不可为空");
        }
        User user= userMapper.selectByAccountInfo(username);
        if (user == null) {
            throw new UsernameNotFoundException("用户名或暗码过错,请从头输入");
        }
        return new User(username, user.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(diners.getRoles()));
    }
}

Oauth2参数装备类

读取application.yaml文件中的Oauth2装备信息,并封装到ClientOAuth2DataConfiguration类

package com.example.demo.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
 * 客户端装备类
 */
@Component
@ConfigurationProperties(prefix = "client.oauth2")
@Data
public class ClientOAuth2DataConfiguration {
    /**
     * 客户端标识ID
     */
    private String clientId;
    /**
     * 客户端安全码
     */
    private String secret;
    /**
     * 授权类型
     */
    private String[] grantTypes;
    /**
     * token有用期
     */
    private int tokenValidityTime;
    /**
     * refresh-token有用期
     */
    private int refreshTokenValidityTime;
    /**
     * 客户端拜访规模
     */
    private String[] scopes;
}

授权服务装备

package com.example.demo.config;
import com.example.demo.service.UserService;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import javax.annotation.Resource;
/**
 * 授权服务装备
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfiguration extends AuthorizationServerConfigurerAdapter {
    /**
     * RedisTokenSore
     */
    @Resource
    private RedisTokenStore redisTokenStore;
    /**
     * 认证办理目标
     */
    @Resource
    private AuthenticationManager authenticationManager;
    /**
     * 暗码编码器
     */
    @Resource
    private PasswordEncoder passwordEncoder;
    /**
     * 客户端装备类
     */
    @Resource
    private ClientOAuth2DataConfiguration clientOAuth2DataConfiguration;
    /**
     * 登录校验
     */
    @Resource
    private UserService userService;
    /**
     * 装备令牌端点安全束缚
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        // 答应拜访token的公钥,默许/oauth/token_key是受维护的
        security.tokenKeyAccess("permitAll()")
                // 答应检查token的状况,默许/oauth/check_token是受维护的
                .checkTokenAccess("permitAll()");
    }
    /**
     * 客户端装备 - 授权模型
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory().withClient(clientOAuth2DataConfiguration.getClientId()) // 客户端标识 ID
                .secret(passwordEncoder.encode(clientOAuth2DataConfiguration.getSecret())) // 客户端安全码
                .authorizedGrantTypes(clientOAuth2DataConfiguration.getGrantTypes()) // 授权类型
                .accessTokenValiditySeconds(clientOAuth2DataConfiguration.getTokenValidityTime()) // token 有用期
                .refreshTokenValiditySeconds(clientOAuth2DataConfiguration.getRefreshTokenValidityTime()) // 改写 token 的有用期
                .scopes(clientOAuth2DataConfiguration.getScopes()); // 客户端拜访规模
    }
    /**
     * 装备授权以及令牌的拜访端点和令牌服务
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 认证器
        endpoints.authenticationManager(authenticationManager)
                // 具体登录的办法
                .userDetailsService(userService)
                // token 存储的方法:Redis
                .tokenStore(redisTokenStore);
    }
}

履行测验

恳求 localhost:8888/oauth/token

参数设置

Spring Security OAuth2搭建认证授权中心、资源服务中心、并结合网关校验的完整详细示例
Spring Security OAuth2搭建认证授权中心、资源服务中心、并结合网关校验的完整详细示例
履行恳求
Spring Security OAuth2搭建认证授权中心、资源服务中心、并结合网关校验的完整详细示例
检查Redis
Spring Security OAuth2搭建认证授权中心、资源服务中心、并结合网关校验的完整详细示例

增强令牌

增强令牌就是丰厚、自定义令牌包含的信息,这部分信息是客户端能直接看到的

重构端点

重构/oauth/token端点

/**
 * Oauth2控制器
 */
@RestController
@RequestMapping("oauth")
public class OAuthController {
    @Resource
    private TokenEndpoint tokenEndpoint;
    @Resource
    private HttpServletRequest request;
    /**
     * 自定义Token回来目标
     *
     * @param principal
     * @param parameters
     * @return
     * @throws HttpRequestMethodNotSupportedException
     */
    @PostMapping("token")
    public HashMap<String, Object> postAccessToken(Principal principal, @RequestParam Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
        OAuth2AccessToken auth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
        DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) auth2AccessToken;
        Map<String, Object> data = new LinkedHashMap(token.getAdditionalInformation());
        data.put("accessToken", token.getValue());
        data.put("expireIn", token.getExpiresIn());
        data.put("scopes", token.getScope());
        if (token.getRefreshToken() != null) {
            data.put("refreshToken", token.getRefreshToken().getValue());
        }
        data.put("path", request.getServletPath());
        return BaseUtil.back(1, data);
    }
}

履行测验

Spring Security OAuth2搭建认证授权中心、资源服务中心、并结合网关校验的完整详细示例

重构令牌

创立SignInIdentity登录认证目标类完成UserDetails

package com.example.demo.model;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
 * 登录认证目标
 */
@Getter
@Setter
public class SignInIdentity implements UserDetails {
    /**
     * 主键
     */
    private Integer id;
    /**
     * 用户名
     */
    private String username;
    /**
     * 昵称
     */
    private String nickname;
    /**
     * 暗码
     */
    private String password;
    /**
     * 手机号
     */
    private String phone;
    /**
     * 邮箱
     */
    private String email;
    /**
     * 头像
     */
    private String avatarUrl;
    /**
     * 人物
     */
    private String roles;
    /**
     * 是否有用 0=无效 1=有用
     */
    private int isValid;
    /**
     * 人物集合, 不能为空
     */
    private List<GrantedAuthority> authorities;
    /**
     * 获取人物信息
     *
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        if (StrUtil.isNotBlank(this.roles)) {
            String[] strings = this.roles.split(",");
            // 获取数据库中的人物信息
            this.authorities = Stream.of(strings).map(role -> {
                return new SimpleGrantedAuthority(role);
            }).collect(Collectors.toList());
        } else {
            // 如果人物为空则设置为ROLE_USER
            this.authorities = AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER");
        }
        return this.authorities;
    }
    @Override
    public String getPassword() {
        return this.password;
    }
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    @Override
    public boolean isEnabled() {
        return this.isValid != 0;
    }
}

修正登录认证

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (StrUtil.hasBlank(username)) {
            throw new RuntimeException("用户名不可为空");
        }
        Diners diners = dinersMapper.selectByAccountInfo(username);
        if (diners == null) {
            throw new UsernameNotFoundException("用户名或暗码过错,请从头输入");
        }
        // 初始化登录认证目标
        SignInIdentity signInIdentity = new SignInIdentity();
        // 拷贝属性
        BeanUtils.copyProperties(diners, signInIdentity);
        return signInIdentity;
        // return new User(username, diners.getPassword(), AuthorityUtils.commaSeparatedStringToAuthorityList(diners.getRoles()));
    }

令牌增强

    /**
     * 装备授权以及令牌的拜访端点和令牌服务
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 认证器
        endpoints.authenticationManager(authenticationManager)
                // 具体登录的办法
                .userDetailsService(userService)
                // token 存储的方法:Redis
                .tokenStore(redisTokenStore)
                // 令牌增强目标,增强回来的结果
                .tokenEnhancer((accessToken, authentication) -> {
                    // 获取登录用户的信息,然后设置
                    SignInIdentity signInIdentity = (SignInIdentity) authentication.getPrincipal();
                    LinkedHashMap<String, Object> map = new LinkedHashMap<>();
                    map.put("nickname", signInIdentity.getNickname());
                    map.put("avatarUrl", signInIdentity.getAvatarUrl());
                    DefaultOAuth2AccessToken token = (DefaultOAuth2AccessToken) accessToken;
                    token.setAdditionalInformation(map);
                    return token;
                });
    }

履行测验

恳求 localhost:8888/oauth/token

Spring Security OAuth2搭建认证授权中心、资源服务中心、并结合网关校验的完整详细示例

资源服务中心

登录成功,得到token,经过token获取资源

认证反常装备

创立MyAuthenticationEntryPoint类,处理认证失利出现反常时的处理逻辑。

package com.example.demo.config;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.example.demo.utils.BaseUtil;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.HashMap;
/**
 * 认证失利处理
 */
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Resource
    private ObjectMapper objectMapper;
    /**
     * 认证失利处理逻辑
     *
     * @param request
     * @param response
     * @param authException
     * @throws IOException
     */
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
        // 回来 JSON
        response.setContentType("application/json;charset=utf-8");
        // 状况码 401
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        // 写出
        PrintWriter out = response.getWriter();
        String errorMessage = authException.getMessage();
        if (StrUtil.isBlank(errorMessage)) {
            errorMessage = "登录失效!";
        }
        HashMap<String, Object> result = BaseUtil.back(0, errorMessage, errorMessage);
        // ResultInfo result = ResultInfoUtil.buildError(ApiConstant.ERROR_CODE, errorMessage, request.getRequestURI());
        out.write(objectMapper.writeValueAsString(result));
        out.flush();
        out.close();
    }
}

创立资源服务

package com.example.demo.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import javax.annotation.Resource;
/**
 * 资源服务
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter {
    @Resource
    private MyAuthenticationEntryPoint authenticationEntryPoint;
    /**
     * 装备放行的资源
     *
     * @param http
     * @throws Exception
     */
    @Override
    public void configure(HttpSecurity http) throws Exception {
        //一切恳求必须认证经过
        http.authorizeRequests()
                //其他地址需要认证授权;
                .anyRequest()
                .authenticated()
                .and()
                //下边的途径放行
                .requestMatchers()
                .antMatchers("/user/**");
    }
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) {
        resources.authenticationEntryPoint(authenticationEntryPoint);
    }
}

提供资源

package com.example.demo.controller;
import com.example.demo.model.SignInIdentity;
import com.example.demo.utils.BaseUtil;
import io.micrometer.core.instrument.util.StringUtils;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2RefreshToken;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
/**
 * 用户中心
 */
@RestController
public class UserController {
    @Resource
    private RedisTokenStore redisTokenStore;
    /**
     * 获取登录用户的信息
     *
     * @param authentication
     * @return
     */
    @GetMapping("user/getLoginUser")
    public HashMap<String, Object> getCurrentUser(Authentication authentication) {
        SignInIdentity signInIdentity = (SignInIdentity) authentication.getPrincipal();
        HashMap<String, Object> map = new HashMap<>();
        map.put("username", signInIdentity.getUsername());
        map.put("phone", signInIdentity.getPhone());
        map.put("email", signInIdentity.getEmail());
        return BaseUtil.back(1, "获取资源成功", map);
    }
    /**
     * 安全退出
     *
     * @param access_token
     * @param authorization
     * @return
     */
    @GetMapping("user/logout")
    public HashMap<String, Object> logout(String access_token, String authorization) {
        // 判别access_token是否为空,为空将authorization赋值给access_token
        if (StringUtils.isBlank(access_token)) {
            access_token = authorization;
        }
        // 判别authorization是否为空
        if (StringUtils.isBlank(access_token)) {
            return BaseUtil.back(1, "退出成功");
        }
        // 判别bearer token是否为空
        if (access_token.toLowerCase().contains("bearer ".toLowerCase())) {
            access_token = access_token.toLowerCase().replace("bearer ", "");
        }
        // 铲除redis token信息
        OAuth2AccessToken oAuth2AccessToken = redisTokenStore.readAccessToken(access_token);
        if (oAuth2AccessToken != null) {
            redisTokenStore.removeAccessToken(oAuth2AccessToken);
            OAuth2RefreshToken refreshToken = oAuth2AccessToken.getRefreshToken();
            redisTokenStore.removeRefreshToken(refreshToken);
        }
        return BaseUtil.back(1, "退出成功");
    }
}

履行测验

恳求localhost:8888/oauth/token获取token

{
	"code": 1,
	"message": "Successful.",	
	"data": {
		"nickname": "test",
		"avatarUrl": "/test",
		"accessToken": "2cf71a49-1f62-4e93-b27c-7cb0b4419ab4",
		"expireIn": 2588653,
		"scopes": [
			"api"
		],
		"refreshToken": "154aefe0-a0fa-43d4-91fb-c70b1e2998e4",
		"path": "/oauth/token"
	}
}

运用token获取服务资源,有两种方法:

方法一:

恳求localhost:8888/user/getLoginUser?access_token=2cf71a49-1f62-4e93-b27c-7cb0b4419ab4获取资源

{
	"msg": "获取资源成功",
	"code": 1,
	"data": {
		"phone": "13666666666",
		"email": null,
		"username": "test"
	}
}

方法二:

恳求localhost:8888/user/getLoginUser,运用Bearer auth认证

Spring Security OAuth2搭建认证授权中心、资源服务中心、并结合网关校验的完整详细示例
获取资源

{
	"msg": "获取资源成功",
	"code": 1,
	"data": {
		"phone": "13666666666",
		"email": null,
		"username": "test"
	}
}

校验token

恳求 localhost:8888/oauth/check_token?token=2cf71a49-1f62-4e93-b27c-7cb0b4419ab4

校验token成功时:

{
	"avatarUrl": "/test",
	"user_name": "test",
	"scope": [
		"api"
	],
	"nickname": "test",
	"active": true,
	"exp": 1629734432,
	"authorities": [
		"ROLE_USER"
	],
	"client_id": "appId"
}

校验token失利时:

{
	"error": "invalid_token",
	"error_description": "Token was not recognised"
}

安全退出

1.恳求localhost:8888/user/logout?access_token=2cf71a49-1f62-4e93-b27c-7cb0b4419ab4

2.恳求localhost:8888/user/logout,运用Bearer auth认证

{
	"msg": "退出成功",
	"code": 1,
	"data": null
}

退出后再次恳求资源

{
	"msg": "Invalid access token: 2cf71a49-1f62-4e93-b27c-7cb0b4419ab4",
	"code": 0,
	"data": "Invalid access token: 2cf71a49-1f62-4e93-b27c-7cb0b4419ab4"
}

网关校验

添加依靠

        <!-- spring cloud gateway -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
        </dependency>
        <!-- eureka client -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>

装备application.yml

server:
  port: 9999
spring:
  application:
    name: gateway-server
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true # 开启装备注册中心进行路由功用
          lower-case-service-id: true # 将服务称号转小写
      routes:
        - id: oauth2-server
          uri: lb://oauth2-server
          predicates:
            - Path=/auth/**
          filters:
            - StripPrefix=1
# 自定义参数
secure:
  ignore:
    urls: # 装备白名单途径
      - /actuator/**
      - /auth/oauth/**
      - /user/getLoginUser
      - /user/logout
# 装备 Eureka Server 注册中心
eureka:
  instance:
    prefer-ip-address: true
    instance-id: ${spring.cloud.client.ip-address}:${server.port}
  client:
    service-url:
      defaultZone: http://localhost:8080/eureka/        

途径白名单装备类

/**
 * 网关白名单装备
 */
@Data
@Component
@ConfigurationProperties(prefix = "secure.ignore")
public class IgnoreUrlsConfig {
    private List<String> urls;
}

网关过滤器

package com.example.demo.gateway.filter;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.example.demo.utils.BaseUtil;
import com.example.demo.config.IgnoreUrlsConfig;
import org.apache.commons.lang.StringUtils;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
/**
 * 网关大局过滤器
 */
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
    @Resource
    private IgnoreUrlsConfig ignoreUrlsConfig;
    @Resource
    private RestTemplate restTemplate;
    /**
     * 身份校验处理
     *
     * @param exchange
     * @param chain
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 判别当前的恳求是否在白名单中
        AntPathMatcher pathMatcher = new AntPathMatcher();
        boolean flag = false;
        String path = exchange.getRequest().getURI().getPath();
        for (String url : ignoreUrlsConfig.getUrls()) {
            if (pathMatcher.match(url, path)) {
                flag = true;
                break;
            }
        }
        // 白名单放行
        if (flag) {
            return chain.filter(exchange);
        }
        // 获取 access_token
        String access_token = exchange.getRequest().getQueryParams().getFirst("access_token");
        // 判别access_token是否为空
        if (StringUtils.isBlank(access_token)) {
            return this.writeError(exchange, "请登录");
        }
        // 校验token是否有用
        String checkTokenUrl = "http://oauth2-server/oauth/check_token?token=".concat(access_token);
        try {
            // 发送远程恳求,验证 token
            ResponseEntity<String> entity = restTemplate.getForEntity(checkTokenUrl, String.class);
            // token无效业务逻辑处理
            if (entity.getStatusCode() != HttpStatus.OK) {
                return this.writeError(exchange, "恳求失利");
            }
            if (StringUtils.isBlank(entity.getBody())) {
                return this.writeError(exchange, "获取token失利");
            }
        } catch (Exception e) {
            return this.writeError(exchange, "token校验失利");
        }
        // 放行
        return chain.filter(exchange);
    }
    /**
     * 网关过滤器的排序,数字越小优先级越高
     *
     * @return
     */
    @Override
    public int getOrder() {
        return 0;
    }
    @Resource
    private ObjectMapper objectMapper;
    public Mono<Void> writeError(ServerWebExchange exchange, String error) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.OK);
        response.getHeaders().add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
        HashMap<String, Object> back = BaseUtil.back(0, error);
        String resultInfoJson;
        DataBuffer buffer = null;
        try {
            resultInfoJson = objectMapper.writeValueAsString(back);
            buffer = response.bufferFactory().wrap(resultInfoJson.getBytes(StandardCharsets.UTF_8));
        } catch (JsonProcessingException ex) {
            ex.printStackTrace();
        }
        return response.writeWith(Mono.just(buffer));
    }
}

履行测验

恳求localhost:9999/auth/oauth/token获取token

{
	"code": 1,
	"message": "Successful.",	
	"data": {
		"nickname": "test",
		"avatarUrl": "/test",
		"accessToken": "7a3d7102-39eb-4d02-be4c-9a705c9db616",
		"expireIn": 2591999,
		"scopes": [
			"api"
		],
		"refreshToken": "6c974314-a13c-473a-8cdc-fb8373b3cce5",
		"path": "/oauth/token"
	}
}

恳求localhost:9999/auth/user/getLoginUser?access_token=7a3d7102-39eb-4d02-be4c-9a705c9db616获取服务资源

{
	"msg": "获取资源成功",
	"code": 1,
	"data": {
		"phone": "13666666666",
		"email": null,
		"username": "test"
	}
}

恳求localhost:9999/auth/user/logout?access_token=7a3d7102-39eb-4d02-be4c-9a705c9db616安全退出

{
	"msg": "退出成功",
	"code": 1,
	"data": null
}