1. 概述
在微服務(wù)項目中,需要對整個微服務(wù)系統(tǒng)進行權(quán)限校驗,通常有兩種方案,其一是每個微服務(wù)各自鑒權(quán),其二是在網(wǎng)關(guān)統(tǒng)一鑒權(quán),第二種方案只需要一次鑒權(quán)就行,避免了每個微服務(wù)重復(fù)鑒權(quán)的麻煩,本文以網(wǎng)關(guān)統(tǒng)一鑒權(quán)為例介紹如何搭建微服務(wù)鑒權(quán)項目。
本文案例中共有四個微服務(wù)模塊,服務(wù)注冊中心、網(wǎng)關(guān)服務(wù)、鑒權(quán)服務(wù)和業(yè)務(wù)提供者
案例中使用組件版本號如下:
組件 | 版本 |
---|---|
JDK | 11 |
SpringBoot | 2.7.9 |
SpringCloud | 2021.0.6 |
Mybatis-Plus | 3.5.3.1 |
jjwt | 0.11.5 |
2. 鑒權(quán)微服務(wù)
新建一個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業(yè)務(wù)類
@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類實現(xiàn)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類實現(xiàn)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只有用戶名和密碼字段,實體類、持久層接口和業(yè)務(wù)類接口都比較簡單,文中就不一一列舉
2.5. 編寫權(quán)限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. 網(wǎng)關(guān)微服務(wù)
新建一個SpringBoot項目,命名為springcloud-gateway文章來源:http://www.zghlxwxcb.cn/news/detail-537792.html
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. 編寫權(quán)限過濾器類
@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. 測試
依次啟動注冊中心服務(wù)、網(wǎng)關(guān)服務(wù)、鑒權(quán)服務(wù)和業(yè)務(wù)提供服務(wù)
postman發(fā)起post請求http://localhost:8000/auth/token獲取token
postman發(fā)起get請求http://localhost:8000/provider/server/info,將上面獲取的token攜帶上,如下配置
Type類型選擇No Auth,再次發(fā)起請求
查看后臺日志會發(fā)現(xiàn)是因為沒有token
說明:本文只是簡單實現(xiàn)了gateway實現(xiàn)統(tǒng)一鑒權(quán)功能,有些地方還需要小伙伴自行優(yōu)化,例如沒有token異常提示可以返回給前端文章來源地址http://www.zghlxwxcb.cn/news/detail-537792.html
到了這里,關(guān)于SpringCloud搭建微服務(wù)之Gateway+Jwt實現(xiàn)統(tǒng)一鑒權(quán)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!