SpringCloud(10)使用Spring Cloud OAuth2和JWT保護微服務

採用Spring Security AOuth2 和 JWT 的方式,避免每次請求都須要遠程調度 Uaa 服務。採用Spring Security OAuth2 和 JWT 的方式,Uaa 服務只驗證一次,返回JWT。返回的 JWT 包含了用戶的全部信息,包括權限信息。html

1.什麼是JWT?

JSON Web Token(JWT)是一種開放的標準(RFC 7519),JWT定義了一種緊湊且自包含的標準,該標準旨在將各個主體的信息包裝爲 JSON 對象。主體信息是經過數字簽名進行加密和驗證的。常使用 HMAC 算法或 RSA(公鑰/私鑰的非對稱性加密)算法對JWT進行簽名,安全性很高。java

JWT 特色:mysql

  • 緊湊型:數據體積小,可經過 POST 請求參數或 HTTP 請求頭髮送。
  • 自包含:JWT包含了主體的全部信息,避免了每一個請求都須要向Uaa服務驗證身份,下降了服務器的負載。

2.JWT的結構

JWT結構:web

  • Header(頭)
  • Payload(有效載荷)
  • Signature(簽名)

所以,JWT的一般格式是:xxxxx.yyyyy.zzzzz算法

(1)Headerspring

Header 一般是由兩部分組成:令牌的類型(即JWT)和使用的算法類型,如 HMAC、SHA256和RSA。例如:sql

{
    "typ": "JWT",
    "alg": "HS256"
}

將 Header 用 Base64 編碼做爲 JWT 的第一部分。數據庫

(2)Payloadapache

這是 JWT 的第二部分,包含了用戶的一些信息和Claim(聲明、權利)。有3類型的 Claim:保留、公開和私人。json

{
    "sub": "123456789",
    "name": "John Doe",
    "admin": true
}

將 Payload 用 Base64 編碼做爲 JWT 的第一部分。

(3)Signature

要建立簽名部分,須要將 Base64 編碼後的 Header、Payload 和祕鑰進行簽名,一個典型的格式以下:

HMACSHA256(
    base64UrlEncode(header) + '.' +
    base64UrlEncode(payload),
    secret
)

3.如何使用JWT

認證流程圖以下,客戶端獲取JWT後,之後每次請求都不須要再經過Uaa服務來判斷該請求的用戶以及該用戶的權限。在微服務中,能夠利用JWT實現單點登陸。

4.案例工程架構

三個工程:

  • eureka-server:註冊服務中心,端口8761。這裏再也不演示搭建。
  • auth-service:負責受權,受權須要用戶提供客戶端的 clientId 和 password,以及受權用戶的username和password。這些信息準備無誤以後,auth-service 返回JWT,該 JWT 包含了用戶的基本信息和權限點信息,並經過 RSA 加密。
  • user-service:做爲資源服務,它的資源以及被保護起來了,須要相應的權限才能訪問。user-service 服務獲得用戶請求的 JWT 後,先經過公鑰解密JWT,獲得該JWT對應的用戶的信息和用戶的權限信息,再判斷該用戶是否有權限訪問該資源。

工程架構圖:

5.構建auth-service工程

1.新建Spring Boot工程,取名爲 auth-service,其完整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">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.example</groupId>
    <artifactId>auth-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>auth-service</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Dalston.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
            <!--防止jks文件被mavne編譯致使不可用-->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-resources-plugin</artifactId>
                <configuration>
                    <nonFilteredFileExtensions>
                        <nonFilteredFileExtension>cert</nonFilteredFileExtension>
                        <nonFilteredFileExtension>jks</nonFilteredFileExtension>
                    </nonFilteredFileExtensions>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

2.配置application.yml文件

spring:
  application:
    name: auth-service
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spring-cloud-auth?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
    username: root
    password: 123456
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
server:
  port: 9999
eureka:
  client:
    serviceUrl:
      defaultZone: http://localhost:8761/eureka/

3.配置Spring Security

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf().disable() //關閉CSRF
                .exceptionHandling()
                .authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
            .and()
                .authorizeRequests()
                .antMatchers("/**").authenticated()
            .and()
                .httpBasic();
    }

    @Autowired
    UserServiceDetail userServiceDetail;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userServiceDetail)
                .passwordEncoder(new BCryptPasswordEncoder()); //密碼加密
    }
}

UserServiceDetail.java

@Service
public class UserServiceDetail implements UserDetailsService {
    @Autowired
    private UserDao userRepository;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        return userRepository.findByUsername(username);
    }
}

UserDao.java

@Repository
public interface UserDao extends JpaRepository<User, Long> {

    User findByUsername(String username);
}

User對象和上一篇文章的內容同樣,須要實現UserDetails接口,Role對象須要實現GrantedAuthority接口.

@Entity
public class User implements UserDetails, Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false,  unique = true)
    private String username;

    @Column
    private String password;

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "user_role", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
            inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id"))
    private List<Role> authorities;


    public User() {
    }

    public Long getId() {
        return id;
    }
    public void setId(Long id) {
        this.id = id;
    }

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

    public void setAuthorities(List<Role> authorities) {
        this.authorities = authorities;
    }

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

    public void setUsername(String username) {
        this.username = username;
    }

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

    public void setPassword(String password) {
        this.password = password;
    }

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

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

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

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

}
@Entity
public class Role implements GrantedAuthority {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(nullable = false)
    private String name;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Override
    public String getAuthority() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return name;
    }
}

4.配置 Authorization Server

在 OAuth2Config 這個類中配置 AuthorizationServer,其代碼以下:

@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory() //將客戶端的信息存儲在內存中
                .withClient("user-service") //建立了一個Client爲"user-service"的客戶端
                .secret("123456")
                .scopes("service") //客戶端的域
                .authorizedGrantTypes("refresh_token", "password") //配置類驗證類型爲 refresh_token和password
                .accessTokenValiditySeconds(12*300); //5min過時
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.tokenStore(tokenStore()).tokenEnhancer(jwtTokenEnhancer()).authenticationManager(authenticationManager);
    }

    @Autowired
    @Qualifier("authenticationManagerBean")
    private AuthenticationManager authenticationManager;

    @Bean
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtTokenEnhancer());
    }

    @Bean
    protected JwtAccessTokenConverter jwtTokenEnhancer() {
        //注意此處須要相應的jks文件
        KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("fzp-jwt.jks"), "fzp123".toCharArray());
        JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
        converter.setKeyPair(keyStoreKeyFactory.getKeyPair("fzp-jwt"));
        return converter;
    }
}

5.生成 jks 文件

配置 JwtTokenStore 時須要使用 jks 文件做爲 Token 加密的祕鑰。

jks 文件須要Java keytool工具,保證Java環境變量沒問題,打開計算機終端,輸入命令:

keytool -genkeypair -alias fzp-jwt -validity 3650 -keyalg RSA -dname "CN=jwt,OU=jtw,O=jwt,L=zurich,S=zurich,C=CH" -keypass fzp123 -keystore fzp-jwt.jks -storepass fzp123

解釋,-alias 選項爲別名,-keypass 和 -storepass 爲密碼選項,-validity 爲配置jks文件過時時間(單位:天)。

獲取的 jks 文件做爲私鑰,只容許 Uaa 服務持有,並用做加密 JWT。也就是把生成的 jks 文件放到 auth-service 工程的resource目錄下。那麼 user-service 這樣的資源服務,是如何解密 JWT 的呢?這時就須要使用 jks 文件的公鑰。獲取 jks 文件的公鑰命令以下:

keytool -list -rfc --keystore fzp-jwt.jks | openssl x509 -inform pem -pubkey

這個命令要求你的計算機上安裝了openSSL(下載地址),而後手動把安裝的openssl.exe所在目錄配置到環境變量。

輸入密碼fzp123後,顯示的信息不少,咱們只提取 PUBLIC KEY,即以下所示:

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlCFiWbZXIb5kwEaHjW+/
7J4b+KzXZffRl5RJ9rAMgfRXHqGG8RM2Dlf95JwTXzerY6igUq7FVgFjnPbexVt3
vKKyjdy2gBuOaXqaYJEZSfuKCNN/WbOF8e7ny4fLMFilbhpzoqkSHiR+nAHLkYct
OnOKMPK1SwmvkNMn3aTEJHhxGh1RlWbMAAQ+QLI2D7zCzQ7Uh3F+Kw0pd2gBYd8W
+DKTn1Tprugdykirr6u0p66yK5f1T9O+LEaJa8FjtLF66siBdGRaNYMExNi21lJk
i5dD3ViVBIVKi9ZaTsK9Sxa3dOX1aE5Zd5A9cPsBIZ12spYgemfj6DjOw6lk7jkG
9QIDAQAB
-----END PUBLIC KEY-----

新建一個 public.cert 文件,將上面的公鑰信息複製到 public.cert 文件中並保存。並將文件放到 user-service 等資源服務的resources目錄下。到目前爲止,Uaa 服務已經搭建完畢。

須要注意的是,Maven 在項目編譯時,可能會將 jks 文件編譯,致使 jks 文件亂碼,最後不可用。須要在工程的 pom 文件中添加如下內容:

<!--防止jks文件被mavne編譯致使不可用-->
<plugin>
       <groupId>org.apache.maven.plugins</groupId>
       <artifactId>maven-resources-plugin</artifactId>
       <configuration>
             <nonFilteredFileExtensions>
                 <nonFilteredFileExtension>cert</nonFilteredFileExtension>
                 <nonFilteredFileExtension>jks</nonFilteredFileExtension>
              </nonFilteredFileExtensions>
        </configuration>
</plugin>

最後,別忘了在啓動類註解@EnableEurekaClient開啓服務註冊.

@SpringBootApplication
@EnableEurekaClient
public class AuthServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(AuthServiceApplication.class, args);
    }
}

6.構建user-service資源服務

1.新建Spring Boot工程,取名爲user-service,其完整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">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.example</groupId>
    <artifactId>user-service</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <name>user-service</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <spring-cloud.version>Dalston.SR1</spring-cloud.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-hystrix</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-feign</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.配置文件application.yml

在工程的配置文件application.yml中,配置程序名爲 user-service,端口號爲 9090,另外,須要配置 feign.hystrix.enable 爲true,即開啓 Feign 的 Hystrix 功能。完整的配置代碼以下:

server:
  port: 9090
eureka:
  client:
    service-url:
      defaultZone: http://localhost:8761/eureka/
spring:
  application:
    name: user-service
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/spring-cloud-auth?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8
    username: root
    password: 123456
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
feign:
  hystrix:
    enabled: true

3.配置Resource Server

在配置Resource Server以前,須要注入 JwtTokenStore 類型的 Bean。

@Configuration
public class JwtConfig {
    @Autowired 
    JwtAccessTokenConverter jwtAccessTokenConverter;

    @Bean
    @Qualifier("tokenStore")
    public TokenStore tokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter);
    }
    
    @Bean
    protected JwtAccessTokenConverter jwtTokenEnhancer() {
        //用做 JWT 轉換器
        JwtAccessTokenConverter converter =  new JwtAccessTokenConverter();
        Resource resource = new ClassPathResource("public.cert");
        String publicKey ;
        try {
            publicKey = new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        converter.setVerifierKey(publicKey); //設置公鑰
        return converter;
    }
}

而後配置 Resource Server

@Configuration
@EnableResourceServer //開啓Resource Server功能
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{
    @Autowired
    TokenStore tokenStore;

    @Override
    public void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/user/login","/user/register").permitAll()
                .antMatchers("/**").authenticated();

    }
    
    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(tokenStore);
    }
    
}

4.新建一個配置類 GlobalMethodSecurityConfig,在此類中經過 @EnableGlobalMethodSecurity(prePostEnabled = true)註解開啓方法級別的安全驗證。

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class GlobalMethodSecurityConfig {

}

5.編寫用戶註冊接口

拷貝auth-service工程的User.java、Role.java 和 UserDao.java 到本工程。

在 Service 層的 UserService 寫一個插入用戶的方法,代碼以下

@Service
public class UserServiceDetail {

    @Autowired
    private UserDao userRepository;

    public User insertUser(String username,String  password){
        User user=new User();
        user.setUsername(username);
        user.setPassword(BPwdEncoderUtil.BCryptPassword(password));
        return userRepository.save(user);
    }

}

BPwdEncoderUtil工具類

public class BPwdEncoderUtil {

    private static final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

    public static String  BCryptPassword(String password){
        return encoder.encode(password);
    }

    public static boolean matches(CharSequence rawPassword, String encodedPassword){
        return encoder.matches(rawPassword,encodedPassword);
    }

}

在 Web 層,在 Controller 中寫一個註冊的 API 接口 「/user/register」,代碼以下

@RestController
@RequestMapping("/user")
public class UserController {
    @Autowired
    UserServiceDetail userServiceDetail;

    @PostMapping("/register")
    public User postUser(@RequestParam("username") String username , @RequestParam("password") String password){
        //參數判斷,省略
       return userServiceDetail.insertUser(username,password);
    }

}

6.編寫用戶登陸接口

在Service層,在 UserServiceDetail 中添加一個 login(登陸)方法,代碼以下:

@Service
public class UserServiceDetail {

    @Autowired
    private AuthServiceClient client;

    public UserLoginDTO login(String username, String password){
        User user=userRepository.findByUsername(username);
        if (null == user) {
            throw new UserLoginException("error username");
        }
        if(!BPwdEncoderUtil.matches(password,user.getPassword())){
            throw new UserLoginException("error password");
        }
        // 獲取token
        JWT jwt=client.getToken("Basic dXNlci1zZXJ2aWNlOjEyMzQ1Ng==","password",username,password);
        // 得到用戶菜單
        if(jwt==null){
            throw new UserLoginException("error internal");
        }
        UserLoginDTO userLoginDTO=new UserLoginDTO();
        userLoginDTO.setJwt(jwt);
        userLoginDTO.setUser(user);
        return userLoginDTO;

    }

}

AuthServiceClient 經過向 auth-service 服務遠程調用「/oauth/token」 API接口,獲取 JWT。在 "/oauth/token" API 接口,獲取JWT。在「/oauth/token」API接口中須要在請求頭傳入 Authorization 信息,並須要傳請求參數認證類型 grant_type、用戶名 username 和密碼 password,代碼以下:

@FeignClient(value = "auth-service",fallback =AuthServiceHystrix.class )
public interface AuthServiceClient {

    @PostMapping(value = "/oauth/token")
    JWT getToken(@RequestHeader(value = "Authorization") String authorization, @RequestParam("grant_type") String type,
                 @RequestParam("username") String username, @RequestParam("password") String password);

}

其中,AuthServiceHystrix 爲AuthServiceClient 的熔斷器,代碼以下:

@Component
public class AuthServiceHystrix implements AuthServiceClient {
    @Override
    public JWT getToken(String authorization, String type, String username, String password) {
        return null;
    }
}

JWT 爲一個 JavaBean,它包含了 access_token、token_type 和 refresh_token 等信息,代碼以下:

public class JWT {
    private String access_token;
    private String token_type;
    private String refresh_token;
    private int expires_in;
    private String scope;
    private String jti;
    //getter setter

UserLoginDTO 包含了一個 User 和一個 JWT 對象,用於返回數據的實體:

public class UserLoginDTO {
    private JWT jwt;
    private User user;
    //setter getter
}

登陸異常類 UserLoginException

public class UserLoginException extends RuntimeException{
    public UserLoginException(String message) {
        super(message);
    }
}

統一異常處理

@ControllerAdvice
@ResponseBody
public class ExceptionHandle {
    @ExceptionHandler(UserLoginException.class)
    public ResponseEntity<String> handleException(Exception e) {

        return new ResponseEntity(e.getMessage(), HttpStatus.OK);
    }
}

在web層的 UserController 類補充一個登陸的API接口「/user/login」.

@PostMapping("/login")
public UserLoginDTO login(@RequestParam("username") String username , @RequestParam("password") String password){
    //參數判斷,省略
    return userServiceDetail.login(username,password);
}

爲了測試權限,再補充一個"/foo"接口,該接口須要「ROLE_ADMIN」權限.

@RequestMapping(value = "/foo", method = RequestMethod.GET)
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public String getFoo() {
    return "i'm foo, " + UUID.randomUUID().toString();
}

最後,在啓動類註解開啓Feign:

@SpringBootApplication
@EnableFeignClients
@EnableEurekaClient
public class UserServiceApplication {
    public static void main(String[] args) {
        SpringApplication.run(UserServiceApplication.class, args);
    }
}

7.啓動工程,開始測試

通過千辛萬苦,終於搭建了一個Demo工程,如今開始依次啓動 eureka-server、auth-service 和 user-service工程。這裏咱們使用PostMan測試編寫的接口。

1.註冊用戶

2.登陸獲取Token

3.訪問/user/foo

複製 access_token到 Header頭部,發起GET請求。

"Authorization":"Bearer {access_token}"

由於沒有權限,訪問被拒絕,咱們手動在數據庫添加"ROLE_ADMIN"權限,並與該用戶關聯。從新登陸並獲取Token,從新請求「/user/foo」接口

總結

在本案例中,用戶經過登陸接口來獲取受權服務的Token 。用戶獲取Token 成功後,在之後每次訪問資源服務的請求中都須要攜帶該Token 。資源服務經過公鑰解密Token ,解密成功後能夠獲取用戶信息和權限信息,從而判斷該Token 所對應的用戶是誰, 具備什麼權限。
這個架構的優勢在於,一次獲取Token , 屢次使用,再也不每次詢問Uaa 服務該Token 所對應的用戶信息和用戶的權限信息。這個架構也有缺點,例如一旦用戶的權限發生了改變, 該Token 中存儲的權限信息並無改變, 須要從新登陸獲取新的Token 。就算從新獲取了Token,若是原來的Token 沒有過時,仍然是可使用的,因此須要根據具體的業務場景來設置Token的過時時間。一種改進方式是將登陸成功後獲取的Token 緩存在網關上,若是用戶的權限更改,將網關上緩存的Token 刪除。當請求通過網關,判斷請求的Token 在緩存中是否存在,若是緩存中不存在該Token ,則提示用戶從新登陸。

參考:《深刻理解Spring Cloud與微服務構建》方誌朋

相關文章
相關標籤/搜索