SpringCloud微服务如何进行权限校验?

news2024/11/18 15:30:42

一、概述:

1、什么是Spring Cloud?

SpringCloud, 基于SpringBoot提供了一套微服务解决方案,包括服务注册与发现,配置中心,全链路监控,服务网关,负载均衡,熔断器等组件,除了基于NetFlix的开源组件做高度抽象封装之外,还有一些选型中立的开源组件。

SpringCloud利用SpringBoot的开发便利性,巧妙地简化了分布式系统基础设施的开发,SpringCloud为开发人员提供了快速构建分布式系统的一些工具,包括配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等等,他们都可以用SpringBoot的开发风格做到一键启动和部署。

SpringBoot并没有重复造轮子,它只是将目前各家公司开发的比较成熟,经得起实际考研的服务框架组合起来,通过SpringBoot风格进行再封装,屏蔽掉了复杂的配置和实现原理,最终给开发者留出了一套简单易懂,易部署和易维护的分布式系统开发工具包

2、什么是Spring Cloud 与 SpringBoot的关系

  • SpringBoot专注于快速方便的开发单个个体微服务。
  • SpringCloud是关注全局的微服务协调整理治理框架,它将SpringBoot开发的一个个单体微服务整合并管理起来,为各个微服务之间提供:配置管理,服务发现,断路器,路由,微代理,事件总线,全局锁,决策竞选,分布式会话等等集成服务。
  • SpringBoot可以离开SpringCloud独立使用,开发项目,但是SpringCloud离不开SpringBoot,属于依赖关系

二、以前的鉴权方案

1、单机tomcat应用登录检验(方案一 tomcat开启session 共享)

  • sesssion保存在浏览器和应用服务器会话之间
  • 用户登录成功,服务端会保证一个session,会给客户端一个sessionId
  • 客户端访问的时候会把sessionId保存在cookie中,每次请求都会携带这个sessionId

这种方案存在的问题:
1)存信息到内存,会话保持(session会存储在内存里,如果用户量比较大呢?内存就会被沾满)
2)JessionId存到浏览器cookie里面,如果cookie被拦截就有可能发生CSRF攻击(跨站攻击)

2、分布式应用中session共享(方案二redis集群存储登录token)

真实的应用不可能单节点部署,所以就有个多节点登录session共享的问题需要解决

1)tomcat支持session共享,但是有广播风暴;用户量大的时候,占用资源就严重,不推荐
2)使用redis存储token:

服务端使用UUID生成随机64位或者128位token,放入redis中,然后返回给客户端并存储在cookie中,用户每次访问都携带此token,服务端去redis中校验是否有此用户即可,这种方式目前也有很多公司都在使用。但是这种方式不是最优的方案,这种方式需要每次都去redis里拿数据检验一下,在访问量大的时候,每次访问redis也是一种资源(比如网络资源和IO)的消耗,所以现在很多应用已经开始使用我们今天要讲的JWT

这种架构的缺点:
JessionId和用户信息是绑定的,拿到JessionId就能拿到用户信息,一旦JessionId泄露了,别人就有可能CSRF跨域攻击你服务器

三、使用Cloud Gateway+Oauth2+JWT实现统一认证和鉴权

1、什么是JWT:

JWT(八股文):一个开放标准,它定义了一种用于简洁,自包含的用于通信双方之间以「JSON」对象的形式安全传递信息的方法.JWT可以使用「HMAC」算法或者是「RSA」 的公钥密钥对进行签名

简单来说,就是通过一定规范来生成token,然后可以通过解密算法逆向解密token,这样就可以获取用户信息

优点:
1)生产的token可以包含基本信息,比如id、用户昵称、头像等信息,避免再次查库
2)存储在客户端,不占用服务端的内存资源

缺点:
token是经过base64编码,所以可以解码,因此token加密前的对象不应该包含敏感信息,如用户权限,密码等

2、JWT格式组成 头部、负载、签名

header+payload+signature

  • 头部:主要是描述签名算法
  • 负载:主要描述是加密对象的信息,如用户的id等,也可以加些规范里面的东西,如iss签发者,exp 过期时间,sub 面向的用户
  • 签名:主要是把前面两部分进行加密,防止别人拿到token进行base解密后篡改token

3、关于jwt客户端存储

可以存储在cookie,localstorage和sessionStorage里面

4、实战案例

应用架构

我们理想的解决方案应该是这样的,认证服务负责认证,网关负责校验认证和鉴权,其他API服务负责处理自己的业务逻辑。安全相关的逻辑只存在于认证服务和网关服务中,其他服务只是单纯地提供服务而没有任何安全相关逻辑。

服务划分

  • oauth2-gateway:网关服务,负责请求转发和鉴权功能,整合Spring Security+Oauth2;
  • oauth2-auth:Oauth2认证服务,负责对登录用户进行认证,整合Spring Security+Oauth2;
  • oauth2-api:受保护的API服务,用户鉴权通过后可以访问该服务,不整合Spring Security+Oauth2。

如图:
在这里插入图片描述

方案实现

下面介绍下这套解决方案的具体实现,依次搭建认证服务、网关服务和API服务。

oauth2-auth建认证服务
  • 结构图
    在这里插入图片描述

  • 在pom.xml中添加相关依赖,主要是Spring Security、Oauth2、JWT、Redis相关依赖;

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

    <artifactId>oauth2-auth</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.koo</groupId>
            <artifactId>oauth2-common</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.2.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>8.16</version>
        </dependency>
        <!-- redis -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

    </dependencies>
</project>
  • 在application.yml中添加相关配置,主要是Nacos和Redis相关配置;
server:
  port: 2111
spring:
  profiles:
    active: dev
  application:
    name: oauth2-auth
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss
  redis:
    database: 1
    port: 6379
    host: localhost
    password:
management:
  endpoints:
    web:
      exposure:
        include: "*"
  • 使用keytool生成RSA证书jwt.jks,复制到resource目录下,在JDK的bin目录下使用如下命令即可;
keytool -genkey -alias jwt -keyalg RSA -keystore jwt.jks
  • 创建UserServiceImpl类实现Spring Security的UserDetailsService接口,用于加载用户信息;
@Service
public class UserServiceImpl implements UserDetailsService {
 
    private List<UserDTO> userList;
    @Autowired
    private PasswordEncoder passwordEncoder;
 
    @PostConstruct
    public void initData() {
        String password = passwordEncoder.encode("123456");
        userList = new ArrayList<>();
        userList.add(new UserDTO(1L,"macro", password, 1, CollUtil.toList("ADMIN")));
        userList.add(new UserDTO(2L,"andy", password, 1, CollUtil.toList("TEST")));
    }
 
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        List<UserDTO> findUserList = userList.stream().filter(item -> item.getUsername().equals(username)).collect(Collectors.toList());
        if (CollUtil.isEmpty(findUserList)) {
            throw new UsernameNotFoundException(MessageConstant.USERNAME_PASSWORD_ERROR);
        }
        SysUser sysUser = new SysUser(findUserList.get(0));
        if (!sysUser.isEnabled()) {
            throw new DisabledException(MessageConstant.ACCOUNT_DISABLED);
        } else if (!sysUser.isAccountNonLocked()) {
            throw new LockedException(MessageConstant.ACCOUNT_LOCKED);
        } else if (!sysUser.isAccountNonExpired()) {
            throw new AccountExpiredException(MessageConstant.ACCOUNT_EXPIRED);
        } else if (!sysUser.isCredentialsNonExpired()) {
            throw new CredentialsExpiredException(MessageConstant.CREDENTIALS_EXPIRED);
        }
        return sysUser;
    }
 
}
  • 如果你想往JWT中添加自定义信息的话,比如说登录用户的ID,可以自己实现TokenEnhancer接口;

@Component
public class JwtTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        SysUser securityUser = (SysUser) authentication.getPrincipal();
        Map<String, Object> info = new HashMap<>();
        //把用户ID设置到JWT中
        info.put("id", securityUser.getId());
        ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(info);
        return accessToken;
    }
}
  • 添加认证服务相关配置Oauth2ServerConfig,需要配置加载用户信息的服务UserServiceImpl及RSA的钥匙对KeyPair;

@AllArgsConstructor
@Configuration
@EnableAuthorizationServer
public class Oauth2ServerConfig extends AuthorizationServerConfigurerAdapter {
 
    private final PasswordEncoder passwordEncoder;
    private final UserServiceImpl userDetailsService;
    private final AuthenticationManager authenticationManager;
    private final JwtTokenEnhancer jwtTokenEnhancer;
 
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("client-app")
                .secret(passwordEncoder.encode("123456"))
                .scopes("all")
                .authorizedGrantTypes("password", "refresh_token")
                .accessTokenValiditySeconds(3600)
                .refreshTokenValiditySeconds(86400);
    }
 
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        TokenEnhancerChain enhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> delegates = new ArrayList<>();
        delegates.add(jwtTokenEnhancer); 
        delegates.add(accessTokenConverter());
        enhancerChain.setTokenEnhancers(delegates); //配置JWT的内容增强器
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService) //配置加载用户信息的服务
                .accessTokenConverter(accessTokenConverter())
                .tokenEnhancer(enhancerChain);
    }
 
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security.allowFormAuthenticationForClients();
    }
 
    @Bean
    public JwtAccessTokenConverter accessTokenConverter() {
        JwtAccessTokenConverter jwtAccessTokenConverter = new JwtAccessTokenConverter();
        jwtAccessTokenConverter.setKeyPair(keyPair());
        return jwtAccessTokenConverter;
    }
 
    @Bean
    public KeyPair keyPair() {
        //从classpath下的证书中获取秘钥对
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("jwt.jks"), "123456".toCharArray());
        return keyStoreKeyFactory.getKeyPair("jwt", "123456".toCharArray());
    }
 
}
  • 配置Spring Security,允许获取公钥接口的访问;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .requestMatchers(EndpointRequest.toAnyEndpoint()).permitAll()
                .antMatchers("/rsa/publicKey").permitAll()
                .anyRequest().authenticated();
    }
 
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
 
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
 
}
  • 实体类
@Data
@NoArgsConstructor
public class SysUser implements UserDetails {

    private Long id;

    private String username;

    private String password;

    private UserDTO user;

    public SysUser(UserDTO user) {
        this.user = user;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }

    @Override
    public boolean isAccountNonExpired() {
        return false;
    }

    @Override
    public boolean isAccountNonLocked() {
        return false;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return false;
    }

    @Override
    public boolean isEnabled() {
        return false;
    }
}

![在这里插入图片描述](https://img-blog.csdnimg.cn/f3bdde2ef9ae4838b4be801b07bfe606.png

oauth2-gateway网关

接下来我们就可以搭建网关服务了,它将作为Oauth2的资源服务、客户端服务使用,对访问微服务的请求进行统一的校验认证和鉴权操作。

  • 结构图
    在这里插入图片描述

  • 在pom.xml中添加相关依赖,主要是Gateway、Oauth2和JWT相关依赖;

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

    <modelVersion>4.0.0</modelVersion>

    <artifactId>oauth2-gateway</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.koo</groupId>
            <artifactId>oauth2-common</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-gateway</artifactId>
            <version>2.2.9.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-resource-server</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-client</artifactId>
            <version>5.3.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-jose</artifactId>
            <version>5.3.3.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>8.16</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-core</artifactId>
        </dependency>
    </dependencies>

</project>
  • 在application.yml中添加相关配置,主要是路由规则的配置、Oauth2中RSA公钥的配置及路由白名单的配置;
server:
  port: 2112
spring:
  profiles:
    active: dev
  application:
    name: oauth2-gateway
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
    gateway:
      routes: #配置路由规则
        - id: oauth2-api-route
          uri: lb://oauth2-api
          predicates:
            - Path=/api/**
          filters:
            - StripPrefix=1
        - id: oauth2-auth-route
          uri: lb://oauth2-auth
          predicates:
            - Path=/auth/**
          filters:
            - StripPrefix=1
      discovery:
        locator:
          enabled: true #开启从注册中心动态创建路由的功能
          lower-case-service-id: true #使用小写服务名,默认是大写
  security:
    oauth2:
      resourceserver:
        jwt:
          jwk-set-uri: 'http://localhost:9401/rsa/publicKey' #配置RSA的公钥访问地址
  redis:
    database: 0
    port: 6379
    host: localhost
    password:
secure:
  ignore:
    urls: #配置白名单路径
      - "/actuator/**"
      - "/auth/oauth/
  • 对网关服务进行配置安全配置,由于Gateway使用的是WebFlux,所以需要使用@EnableWebFluxSecurity注解开启;

  • 在WebFluxSecurity中自定义鉴权操作需要实现ReactiveAuthorizationManager接口;

@Component
public class AuthorizationManager implements ReactiveAuthorizationManager<AuthorizationContext> {
    @Autowired
    private RedisTemplate<String,Object> redisTemplate;
 
    @Override
    public Mono<AuthorizationDecision> check(Mono<Authentication> mono, AuthorizationContext authorizationContext) {
        //从Redis中获取当前路径可访问角色列表
        URI uri = authorizationContext.getExchange().getRequest().getURI();
        Object obj = redisTemplate.opsForHash().get(RedisConstant.RESOURCE_ROLES_MAP, uri.getPath());
        List<String> authorities = Convert.toList(String.class,obj);
        authorities = authorities.stream().map(i -> i = AuthConstant.AUTHORITY_PREFIX + i).collect(Collectors.toList());
        //认证通过且角色匹配的用户可访问当前路径
        return mono
                .filter(Authentication::isAuthenticated)
                .flatMapIterable(Authentication::getAuthorities)
                .map(GrantedAuthority::getAuthority)
                .any(authorities::contains)
                .map(AuthorizationDecision::new)
                .defaultIfEmpty(new AuthorizationDecision(false));
    }
 
}
  • 这里我们还需要实现一个全局过滤器AuthGlobalFilter,当鉴权通过后将JWT令牌中的用户信息解析出来,然后存入请求的Header中,这样后续服务就不需要解析JWT令牌了,可以直接从请求的Header中获取到用户信息。
@Component
public class AuthGlobalFilter implements GlobalFilter, Ordered {
 
    private static Logger LOGGER = LoggerFactory.getLogger(AuthGlobalFilter.class);
 
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (StringUtils.isEmpty(token)) {
            return chain.filter(exchange);
        }
        try {
            //从token中解析用户信息并设置到Header中去
            String realToken = token.replace("Bearer ", "");
            JWSObject jwsObject = JWSObject.parse(realToken);
            String userStr = jwsObject.getPayload().toString();
            LOGGER.info("AuthGlobalFilter.filter() user:{}",userStr);
            ServerHttpRequest request = exchange.getRequest().mutate().header("user", userStr).build();
            exchange = exchange.mutate().request(request).build();
        } catch (java.text.ParseException e) {
            e.printStackTrace();
        }
        return chain.filter(exchange);
    }
 
    @Override
    public int getOrder() {
        return 0;
    }
}
  • 配置忽略路径
@Getter
@Setter
@ConfigurationProperties(prefix = "secure.ignored")
public class IgnoreUrlsConfig {
    private List<String> urls = new ArrayList<>();
}
  • 对拒绝访问时展现的信息进行处理
@Component
@Slf4j
public class JwtAccessDeniedHandler implements ServerAccessDeniedHandler {

    @Override
    public Mono<Void> handle(ServerWebExchange exchange, AccessDeniedException e) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.FORBIDDEN);
        response.getHeaders().add("Content-Type", "application/json; charset=UTF-8");

        HashMap<String, String> map = new HashMap<>();
        map.put("code", "000000");
        map.put("message", "未授权禁止访问");

        log.error("access forbidden path={}", exchange.getRequest().getPath());

        DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONBytes(map));
        return response.writeWith(Mono.just(dataBuffer));
    }
}
  • 当为登录或者token无效时访问接口,自定义返回结果
@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements ServerAuthenticationEntryPoint {

    @Override
    public Mono<Void> commence(ServerWebExchange exchange, AuthenticationException e) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.FORBIDDEN);
        response.getHeaders().add("Content-Type", "application/json; charset=UTF-8");

        HashMap<String, String> map = new HashMap<>();
        map.put("code", "000000");
        map.put("message", "未授权禁止访问");

        log.error("access forbidden path={}", exchange.getRequest().getPath());

        DataBuffer dataBuffer = response.bufferFactory().wrap(JSON.toJSONBytes(map));
        return response.writeWith(Mono.just(dataBuffer));
    }
}
oauth2-api 模块

最后我们搭建一个API服务,它不会集成和实现任何安全相关逻辑,全靠网关来保护它。

  • 结构图
    在这里插入图片描述

  • 在pom.xml中添加相关依赖;

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

    <artifactId>oauth2-api</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.koo</groupId>
            <artifactId>oauth2-common</artifactId>
            <version>1.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>
  • 在application.yml添加相关配置,很常规的配置;
server:
  port: 2113
spring:
  profiles:
    active: dev
  application:
    name: oauth2-api
  cloud:
    nacos:
      discovery:
        server-addr: localhost:8848
management:
  endpoints:
    web:
      exposure:
        include: "*"
  • 创建一个测试接口,网关验证通过即可访问;
@RestController
public class HelloController {
 
    @GetMapping("/hello")
    public String hello() {
        return "Hello World.";
    }
 
}
  • 创建一个LoginUserHolder组件,用于从请求的Header中直接获取登录用户信息;
@Component
public class LoginUserHolder {
 
    public UserDTO getCurrentUser(){
        //从Header中获取用户信息
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        String userStr = request.getHeader("user");
        JSONObject userJsonObject = new JSONObject(userStr);
        UserDTO userDTO = new UserDTO();
        userDTO.setUsername(userJsonObject.getStr("user_name"));
        userDTO.setId(Convert.toLong(userJsonObject.get("id")));
        userDTO.setRoles(Convert.toList(String.class,userJsonObject.get("authorities")));
        return userDTO;
    }
}
  • 创建一个获取当前用户信息的接口。
@RestController
@RequestMapping("/user")
public class UserController{
 
    @Autowired
    private LoginUserHolder loginUserHolder;
 
    @GetMapping("/currentUser")
    public UserDTO currentUser() {
        return loginUserHolder.getCurrentUser();
    }
 
}
oauth2-common公共模块
  • 结构图:
    在这里插入图片描述

  • pom.xml文件

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

    <artifactId>oauth2-common</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.16</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.8.8</version>
        </dependency>
    </dependencies>
</project>
  • 常量
/**
 * @author charlin
 * @date 2023/5/3 13:00
 * @description
 */

public class MessageConstant {
    public static final String USERNAME_PASSWORD_ERROR = "用户密码错误";
    public static final String ACCOUNT_DISABLED = "账户禁用";
    public static final String ACCOUNT_LOCKED = "账户锁定";
    public static final String ACCOUNT_EXPIRED = "账户过期了";
    public static final String CREDENTIALS_EXPIRED = "认证过期";
}
public class RedisConstant {
    public static final String RESOURCE_ROLES_MAP = "resource_roles_map";
}

功能演示

  • 使用密码模式获取JWT令牌,访问地址:http://localhost:2111/auth/oauth/token
    在这里插入图片描述

  • 使用获取到的JWT令牌访问需要权限的接口,访问地址:http://localhost:2113/api/hello
    在这里插入图片描述

  • 使用获取到的JWT令牌访问获取当前登录用户信息的接口,访问地址:http://localhost:2113/api/user/currentUser
    在这里插入图片描述

  • 当JWT令牌过期时,使用refresh_token获取新的JWT令牌,访问地址:http://localhost:2111/auth/oauth/token
    在这里插入图片描述

  • 使用没有访问权限的andy账号登录,访问接口时会返回如下信息,访问地址:http://localhost:2113/api/hello
    在这里插入图片描述

四、源码下载:

https://gitee.com/charlinchenlin/koo-erp

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/485419.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数据结构(六)—— 二叉树(4)回溯

文章目录 一、题1 257 二叉树的所有路径1.1 写法11.2 写法2 一、题 1 257 二叉树的所有路径 1.1 写法1 递归回溯&#xff1a;回溯是递归的副产品&#xff0c;只要有递归就会有回溯 首先考虑深度优先搜索&#xff1b;而题目要求从根节点到叶子的路径&#xff0c;所以需要前序…

第一章 算法概述

第1章-算法概述 总分&#xff1a;100分 得分&#xff1a;30.0分 1 . 填空题 简单 10分 递归算法必须具备的两个条件是___和___ 回答错误 答案 边界条件或停止条件、递推方程或递归方程 2 . 填空题 中等 10分 冒泡排序时间复杂度是___&#xff0c;堆排序时…

深度学习笔记--本地部署Mini-GPT4

目录 1--前言 2--配置环境依赖 3--下载权重 4--生成 Vicuna 权重 5--测试 6--可能出现的问题 1--前言 本机环境&#xff1a; System: Ubuntu 18.04 GPU: Tesla V100 (32G) CUDA: 10.0 项目地址&#xff1a;https://github.com/Vision-CAIR/MiniGPT-4 2--配置环境依赖 …

18.考虑阶梯式碳交易机制与电制氢的综合能源系统热电优化

说明书 MATLAB代码&#xff1a;考虑阶梯式碳交易机制与电制氢的综合能源系统热电优化 关键词&#xff1a;碳交易 电制氢 阶梯式碳交易 综合能源系统 热电优化 参考文档&#xff1a;《考虑阶梯式碳交易机制与电制氢的综合能源系统热电优化》基本复现 仿真平台&#xff1a;M…

洞车常见问题解决指南

洞车常见问题解决指南 1.研发脚本处理问题1.1 WMS出库单无法审核1.2 OMS入库单无法提交&#xff0c;提示更新中心库存失败1.3 当出现OMS下发成功WMS/TMS/DMS还没有任务的情况时处理方案1.4 调度波次生成或者添加任务系统异常1.5 东鹏出库单部分出库回传之后要求重传1.6 更新订单…

表情符号(emoji)大全,只此一文便够了

本文由 大侠(AhcaoZhu)原创&#xff0c;转载请声明。 链接: https://blog.csdn.net/Ahcao2008 表情符号&#xff08;emoji&#xff09;大全、只此一文便够了 摘要集中展示笑脸和动物人庆贺和物品食品和物交通和地点符号 符号表smileys_and_peopleanimals_and_naturefood_and_dr…

Spring Boot集成ShardingSphere实现数据分片(三) | Spring Cloud 42

一、前言 在前面我们通过以下章节对数据分片有了基础的了解&#xff1a; Spring Boot集成ShardingSphere实现数据分片&#xff08;一&#xff09; | Spring Cloud 40 Spring Boot集成ShardingSphere实现数据分片&#xff08;二&#xff09; | Spring Cloud 41 知道数据分片…

Sentinel --- 简介、流量控制

一、Sentinel 1.1、雪崩问题及解决方案 雪崩问题 微服务中&#xff0c;服务间调用关系错综复杂&#xff0c;一个微服务往往依赖于多个其它微服务。 如图&#xff0c;如果服务提供者I发生了故障&#xff0c;当前的应用的部分业务因为依赖于服务I&#xff0c;因此也会被阻塞。此…

Spring AOP(重点、难点)

Spring AOP&#xff08;重点、难点&#xff09; 文章目录 Spring AOP&#xff08;重点、难点&#xff09;1.aop引入1.1 使用场景与概念引入1.2 以数据校验记录操作日志为例 写一组代码进行递推初始阶段 老老实实一个一个写&#xff1a;阶段一 **将日志和验证方法包装到一个类里…

海洋测绘设备使用总结快讯(2023年5月)

本文主要记录最近海洋测绘设备使用过程中遇到一些小问题和解决方法。 1、侧扫声纳绞车的事情 从去年10月到今年3月一直有一个困扰我们的问题&#xff1a;我们侧扫声纳的铠装缆在租用广西北海渔船且用发电机发电的情况下&#xff0c;能连接Klein3000和Klein4000拖鱼&#xff0…

通过Date类学习面向对象

通过手撸这个类的实现&#xff0c;我们可以学习到构造、析构、运算符重载&#xff0c;拷贝构造等面向对象中重要的知识。 首先先看头文件中类的定义&#xff1a; class Date { public:// 获取某年某月的天数int GetMonthDay(int year, int month);// 全缺省的构造函数Date(in…

算法的时间复杂度和空间复杂度(友友们专属限定版)

&#x1f349;博客主页&#xff1a;阿博历练记 &#x1f4d6;文章专栏&#xff1a;数据结构与算法 &#x1f69a;代码仓库&#xff1a;阿博编程日记 &#x1f339;欢迎关注&#xff1a;欢迎友友们点赞收藏关注哦 文章目录 &#x1f3a8;1.算法的复杂度介绍&#x1f3a8;2.时间复…

坤强服务器安装

记录一下服务器安装做raid和安装系统 raid 0 拆分开分别存在3块硬盘,一块坏了,全部不能用了, 但是存储速度最快 raid 1 具有最高的安全性,备份一份,容量只有总容量的一半 raid 10 先组两个raid1,再组两个raid0 .有raid 1的安全性和50%的使用容量 raid 5 安全性&#xff…

汇编二、51单片机内部结构

1、单片机内部资源 以AT89C51单片机为例&#xff0c;参考数据手册。 Atmel官网&#xff1a; https://www.microchip.com/ (1)1个8位CPU。 (2)4K ROM&#xff0c;128字节RAM。 (3)32个GPIO&#xff1b;定时器(Timer)&#xff1b;串口(UART)&#xff1b;中断系统(Interrupt…

Qt之滑动条和进度条(QSlider、QProgressBar)

文章目录 前言一、QSliderQSlider的常用API信号与槽 二、QProgressBar滑动条和滚动条的常用API 总结 前言 在用户界面设计中&#xff0c;滑动条和进度条是常见的控件。Qt中提供了QProgressBar和QSlider两个类来实现滚动条和滑动条。 一、QSlider 在Qt中&#xff0c;QSlider是…

ChatGPT攥写广告文案-写好广告营销软文的必备要点

chatgpt帮助我们写营销软文 Chat GPT是一款强大的自然语言处理模型&#xff0c;可以辅助您编写优秀的营销软文。下面是几个使用 Chat GPT 更好的编写营销软文的建议&#xff1a; 利用Chat GPT自动摘要 Chat GPT能够将一段较长的营销文本精简成几个关键点&#xff0c;这有利于…

32. 最长有效括号

32. 最长有效括号 难度困难2251 给你一个只包含 ( 和 ) 的字符串&#xff0c;找出最长有效&#xff08;格式正确且连续&#xff09;括号子串的长度。 示例 1&#xff1a; 输入&#xff1a;s "(()" 输出&#xff1a;2 解释&#xff1a;最长有效括号子串是 "…

《C语言技术体系》 学习路线总目录 + 思维导图

目录 前言 正文 思维导图 第1章 流程结构 1.1 初识C语言 1.2 流程结构 1.3 数据类型 1.4 运算符表达式 第2章 指针与数组 2.1 指针基本概念 2.2 一维数组 2.3 二维及多维数组 2.4 指针与数组 第3章 模块化重构 3.1 函数 3.2 typedef类型定义 3.3 enum枚举 3.…

手把手教你使用vue2搭建微前端micro-app

​ 简述 本文主要讲述新手小白怎么搭建micro-app&#xff0c;几乎是每一步都有截图说明。上手应该很简单。 本来我之前已经写了一篇手把手教程了&#xff0c;但是当时写的结个太乱了&#xff0c;趁着五一休假&#xff0c;重新整理了一番&#xff0c;加了文章目录&#xff0c;…

如何显示文件夹的后缀和隐藏文件

© Ptw-cwl 文章目录 前言文件夹后缀隐藏文件 如何设置显示文件夹的后缀和隐藏文件 前言 文件夹后缀 文件后缀是指文件名中最后一个“.”后面的一串字符&#xff0c;用来表示该文件的类型或格式。不同的文件类型有不同的后缀&#xff0c;例如&#xff0c;常见的图片文件…