SpringCloud搭建微服务之Gateway+Jwt实现统一鉴权

1. 概述

在微服务项目中,需要对整个微服务系统进行权限校验,通常有两种方案,其一是每个微服务各自鉴权,其二是在网关统一鉴权,第二种方案只需要一次鉴权就行,避免了每个微服务重复鉴权的麻烦,本文以网关统一鉴权为例介绍如何搭建微服务鉴权项目。
本文案例中共有四个微服务模块,服务注册中心、网关服务、鉴权服务和业务提供者
案例中使用组件版本号如下:

组件版本
JDK11
SpringBoot2.7.9
SpringCloud2021.0.6
Mybatis-Plus3.5.3.1
jjwt0.11.5

2. 鉴权微服务

新建一个SpringBoot项目,命名为springcloud-auth-server

2.1. 引入核心依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>${mybatis-plus.version}</version>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>${jjwt.version}</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>${jjwt.version}</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>${jjwt.version}</version>
</dependency>
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

2.2. 编写JWT业务类

@Service
public class JwtService {

    private static final String SECRET = "JOE38R39GNGRTU49Y534YNIGEYR534YNDEUR7964GEUR735";

    public void validateToken(final String token) {
        Jwts.parserBuilder()
                .setSigningKey(getSignKey())
                .build()
                .parseClaimsJws(token);
    }

    public String generateToken(String username) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, username);
    }

    private String createToken(Map<String, Object> claims, String username) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(username)
                .setIssuedAt(new Date(Instant.now().toEpochMilli()))
                .setExpiration(new Date(Instant.now().toEpochMilli() + 1000 * 30 * 60))
                .signWith(getSignKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    private Key getSignKey() {
        byte[] keyBytes = Decoders.BASE64.decode(SECRET);
        return Keys.hmacShaKeyFor(keyBytes);
    }
}

2.3. 编写配置类

@Configuration
@EnableWebSecurity
public class AuthConfig {

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf()
                .disable()
                .authorizeHttpRequests()
                .antMatchers("/auth/register", "/auth/token", "/auth/validate").permitAll();
        return http.build();
    }

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

    @Bean
    public UserDetailsService userDetailsService() {
        return new CustomUserDetailsService();
    }

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    @Bean
    public AuthenticationProvider authenticationProvider() {
        DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
        authenticationProvider.setUserDetailsService(userDetailsService());
        authenticationProvider.setPasswordEncoder(passwordEncoder());
        return authenticationProvider;
    }
}

2.4. 编写Security用户认证

新建CustomUserDetails类实现UserDetails接口

public class CustomUserDetails implements UserDetails {

    private String username;
    private String password;

    public CustomUserDetails(UserCredential userCredential) {
        this.username = userCredential.getUsername();
        this.password = userCredential.getPassword();
    }

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

    @Override
    public String getPassword() {
        return password;
    }

    @Override
    public String getUsername() {
        return username;
    }

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

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

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

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

编写CustomUserDetailsService类实现UserDetailsService接口

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private AuthService authService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Optional<UserCredential> credential = authService.findUserByUsername(username);
        return credential.map(CustomUserDetails::new).orElseThrow(() -> new UsernameNotFoundException("user not found"));
    }
}

说明:文中用到的用户认证类UserCredential只有用户名和密码字段,实体类、持久层接口和业务类接口都比较简单,文中就不一一列举

2.5. 编写权限Controller类

@RestController
@RequestMapping(value = "/auth")
public class AuthController {

    @Autowired
    private AuthService authService;
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private JwtService jwtService;
    @Autowired
    private AuthenticationManager authenticationManager;

    @PostMapping(value = "/register")
    public ResponseEntity createUser(@RequestBody UserCredential credential) {
        credential.setPassword(passwordEncoder.encode(credential.getPassword()));
        authService.save(credential);
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }

    @PostMapping(value = "/token")
    public ResponseEntity<String> generateToken(@RequestBody AuthRequest authRequest) {
        final Authentication authenticate = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword()));
        if (authenticate.isAuthenticated()) {
            final String token = jwtService.generateToken(authRequest.getUsername());
            return ResponseEntity.status(HttpStatus.OK).body(token);
        } else {
            throw new RuntimeException("invalid access");
        }
    }

    @GetMapping(value = "/validate")
    public ResponseEntity validateToken(@RequestParam String token) {
        jwtService.validateToken(token);
        return ResponseEntity.status(HttpStatus.ACCEPTED).build();
    }
}

3. 网关微服务

新建一个SpringBoot项目,命名为springcloud-gateway

3.1. 引入核心依赖

<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>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>${jjwt.version}</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>${jjwt.version}</version>
</dependency>
<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>${jjwt.version}</version>
</dependency>

3.2. 编写权限过滤器类

@Component
public class AuthenticationFilter extends AbstractGatewayFilterFactory<AuthenticationFilter.Config> {

    @Autowired
    private RouteValidator validator;

    public AuthenticationFilter() {
        super(Config.class);
    }

    @Override
    public GatewayFilter apply(Config config) {
        return ((exchange, chain) -> {
            if (validator.isSecured.test(exchange.getRequest())) {
                if (!exchange.getRequest().getHeaders().containsKey(HttpHeaders.AUTHORIZATION)) {
                    throw new RuntimeException("missing authorization header");
                }
                String authHeader = exchange.getRequest().getHeaders().get(HttpHeaders.AUTHORIZATION).get(0);
                if (null != authHeader && authHeader.startsWith("Bearer ")) {
                    authHeader = authHeader.substring(7);
                }
                try {
                    JwtUtil.validateToken(authHeader);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw new RuntimeException("un authorized access to application");
                }
            }
            return chain.filter(exchange);
        });
    }

    public static class Config {

    }
}

3.3. 编写application.yml配置

spring:
  application:
    name: CLOUD-GATEWAY
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
      routes:
        - id: provider_routh
          uri: lb://CLOUD-PROVIDER-SERVER
          predicates:
            - Path=/provider/server/**
          filters:
            - AuthenticationFilter
        - id: auth_routh
          uri: lb://CLOUD-AUTH-SERVER
          predicates:
            - Path=/auth/**

4. 测试

依次启动注册中心服务、网关服务、鉴权服务和业务提供服务
postman发起post请求http://localhost:8000/auth/token获取token
测试获取token
postman发起get请求http://localhost:8000/provider/server/info,将上面获取的token携带上,如下配置
验证token
Type类型选择No Auth,再次发起请求
验证无token访问
查看后台日志会发现是因为没有token
异常日志
说明:本文只是简单实现了gateway实现统一鉴权功能,有些地方还需要小伙伴自行优化,例如没有token异常提示可以返回给前端

  • 0
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
SpringCloud Gateway是一个基于Spring Framework 5,Spring Boot 2和Project Reactor的反应式API网关,它提供了一种简单而有效的方式来路由请求和过滤器请求,可以用于构建微服务架构中的网关层。 Spring Security是一个强大且灵活的身份验证和访问控制框架,可以集成到Spring应用程序中,用于保护应用程序的安全性。 JWT(JSON Web Token)是一种用于在网络应用间传递声明的一种基于JSON的开放标准。它可以通过数字签名来验证数据的完整性,并使用密钥对数据进行加密。 结合SpringCloud GatewaySpring Security和JWT可以实现一个安全的微服务架构。在这种架构中,SpringCloud Gateway作为网关层负责路由请求和进行安全过滤,Spring Security用于进行身份验证和访问控制,而JWT则用于传递和验证身份信息。 具体实现方案可以参考以下步骤: 1. 在SpringCloud Gateway中配置路由规则,将请求转发到相应的微服务。 2. 在Spring Security中配置身份验证和访问控制规则,例如用户名密码验证、角色授权等。 3. 在用户登录时生成JWT,在每个请求中将JWT作为Authorization头部发送给网关。 4. 网关收到请求后,解析JWT并验证其有效性和签名,如果验证通过,则将请求转发到相应的微服务。 5. 微服务在接收到请求后,可以通过解析JWT获取用户身份信息,并根据用户的权限进行相应的业务处理。 以上是一个简单的概述,具体的实现需要根据实际需求进行配置和开发。希望对你有所帮助!如果你还有其他问题,请继续提问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值