related
-
Spring Cloud Series 1 – Service Registration and Discovery Eureka
-
Spring Cloud – Client calls Rest + Ribbon
-
Spring Cloud Combat Series iii – Declarative client Feign
-
Spring Cloud Series iv – Fuse Hystrix
-
Spring Cloud Combat Series 5 – Service Gateway Zuul
-
Spring Cloud Deployment Series (6) – Distributed Configuration center Spring Cloud Config
-
Spring Cloud Combat Series (7) – Service Link Tracking Spring Cloud Sleuth
-
Spring Cloud Combat Series (8) – Micro service monitoring Spring Boot Admin
-
Spring Cloud OAuth 2.0
-
Single sign-on JWT and Spring Security OAuth
preface
By using JWT in conjunction with Spring Security OAuth2, you can avoid scheduling authentication and authorization services remotely on every request. The resource server only needs to validate once from the authorization server, returning JWT. The JWT returned contains all information about the user, including permission information.
The body of the
1. What is JWT
JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact, self-contained standard designed to wrap the information of individual principals as JSON objects. The principal information is encrypted and authenticated with a digital signature. HMAC algorithm or RSA (public/private key asymmetric encryption) algorithm is often used to sign JWT, which has high security.
-
Compact: Data is small and can be sent via POST request parameters or HTTP request headers.
-
Self-contained: JWT contains all information about the principal, eliminating the need to authenticate to the Uaa service for each request, reducing the load on the server.
2. Structure of JWT
The JWT structure consists of three parts: Header, Payload, and Signature. So the usual format for JWT is xxXXx.yyyyy.zzzzz.
2.1. The Header
The Header is typically made up of two parts: the type of token (that is, JWT) and the type of algorithm used, such as HMAC, SHA256, and RSA. Such as:
{
"typ": "JWT"."alg": "HS256"
}
Copy the code
Make the Header Base64 encoded as the first part of the JWT, and it is not recommended to place sensitive information in the Header of the JWT.
2.2. Content
The second part Payload is the main content part of the JWT, which contains the declaration information. Declarations are declarations about users and other data.
There are three types of declarations: registered, public, and private.
- Registered claims
JWT
One set is providedpredefinedThey are notmandatoryBut it is recommended.JWT
The specifiedSeven of the defaultFields to choose from:
Registration statement | Field meaning |
---|---|
iss | The issuer |
exp | Due to the time |
sub | The theme |
aud | The user |
nbf | Not available before then |
iat | Release time |
jti | ID used to identify the JWT |
-
Public Claims: Can be defined at will.
-
Private Claims: Are used to share information between parties agreeing to use them and are not registered or public claims.
Here is an example of the Payload part:
{
"sub": "123456789"."name": "John Doe"."admin": true
}
Copy the code
Payload is encoded in Base64 as the second part of the JWT. It is not recommended to place sensitive information in the Payload of the JWT.
2.3. Signature
To create a signature part, use the secret key to encrypt the Base64 encoded Header and Payload. The encryption algorithm is as follows:
HMACSHA256(
base64UrlEncode(header) + '. ' +
base64UrlEncode(payload),
secret
)
Copy the code
A signature can be used to verify that a message has not been changed during delivery. For tokens signed with a private key, it can also verify that the sender of JWT is the sender it claims to be.
3. How JWT works
After the client obtains the JWT, it does not need to use the authorization service to determine the user of the request and the permission of the user for each subsequent request. In microservice systems, JWT can be used for single sign-on. The certification flow chart is as follows:
4. Case engineering structure
-
Eureka-server: serves as the registration service center. The port number is 8761. No more demo builds here.
-
Auth-service: as the authorization service, auth-service requires the client Id and Secret of the client, as well as the username and password of the authorized user. After the information is ready, auth-service returns the JWT, which contains the user’s basic information and permission point information, encrypted with the RSA private key.
-
User-service: as a resource service, its resources are protected and can be accessed only by corresponding permissions. After the user-service service obtains the JWT requested by the user, it decrypts the JWT through the public key to obtain the user information and user permission information corresponding to the JWT. Then Spring Security determines whether the user has the permission to access the resource.
The schematic diagram of engineering principle is as follows:
5. Build the auth-service authorization service
- Create a new
auth-service
Project module, completepom.xml
The file configuration is as follows:
<?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>io.github.ostenant.springcloud</groupId>
<artifactId>auth-service</artifactId>
<version>0.0.1 - the 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>
<! -- Prevent JKS files from being compiled by 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>
Copy the code
- Modify the
auth-service
Configuration file ofapplication.yml
The documents are as follows:
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/
Copy the code
- for
auth-service
configurationSpring Security
Secure login management for protectiontoken
issue 和 validationResource interface.
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserServiceDetail userServiceDetail;
@Override
public @Bean AuthenticationManager authenticationManagerBean(a) throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable() / / close CSRF
.exceptionHandling()
.authenticationEntryPoint((request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED))
.and()
.authorizeRequests()
.antMatchers("/ * *").authenticated()
.and()
.httpBasic();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userServiceDetail).passwordEncoder(newBCryptPasswordEncoder()); }}Copy the code
UserServiceDetail.java
@Service
public class UserServiceDetail implements UserDetailsService {
@Autowired
private UserDao userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
returnuserRepository.findByUsername(username); }}Copy the code
UserRepository.java
@Repository
public interface UserRepository extends JpaRepository<User.Long> {
User findByUsername(String username);
}
Copy the code
As in the previous article, the entity class User needs to implement the UserDetails interface, and the entity class Role needs to implement the GrantedAuthority interface.
User.java
@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 Long getId(a) {
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(a) {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Override
public String getPassword(a) {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public boolean isAccountNonExpired(a) {
return true;
}
@Override
public boolean isAccountNonLocked(a) {
return true;
}
@Override
public boolean isCredentialsNonExpired(a) {
return true;
}
@Override
public boolean isEnabled(a) {
return true; }}Copy the code
Role.java
@Entity
public class Role implements GrantedAuthority {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
public Long getId(a) {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public String getAuthority(a) {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString(a) {
returnname; }}Copy the code
- Create a new configuration class
OAuth2Config
forauth-service
configurationCertification services, the code is as follows:
@Configuration
@EnableAuthorizationServer
public class OAuth2Config extends AuthorizationServerConfigurerAdapter {
@Autowired
@Qualifier("authenticationManagerBean")
private AuthenticationManager authenticationManager;
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
// Store the client information in memory
clients.inMemory()
// Configure a client
.withClient("user-service")
.secret("123456")
// Configure the client domain
.scopes("service")
// Set the authentication types to refresh_token and password
.authorizedGrantTypes("refresh_token"."password")
// Set the expiration time of the token to 1h
.accessTokenValiditySeconds(3600 * 1000);
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
// Set the token storage mode to JwtTokenStore
endpoints.tokenStore(tokenStore())
// Configure the enhancer for JWT private key encryption
.tokenEnhancer(jwtTokenEnhancer())
// Configure security authentication management
.authenticationManager(authenticationManager);
}
@Bean
public TokenStore tokenStore(a) {
return new JwtTokenStore(jwtTokenEnhancer());
}
@Bean
protected JwtAccessTokenConverter jwtTokenEnhancer(a) {
// Configure the JKS file
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(new ClassPathResource("fzp-jwt.jks"), "fzp123".toCharArray());
JwtAccessTokenConverter converter = new JwtAccessTokenConverter();
converter.setKeyPair(keyStoreKeyFactory.getKeyPair("fzp-jwt"));
returnconverter; }}Copy the code
- Generated for
Token
encryptedThe private key filefzp-jwt.jks
To generate the JKS file, use the Java Keytool to ensure that the Java environment variables are correct. Run the following command:
$ 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
Copy the code
-alias indicates the alias, -keyalg indicates the encryption algorithm, -keypass and -storepass indicates the password, -keystore indicates the JKS file name, and -validity indicates the JKS file expiration time (unit: day).
The generated JKS file is a private key that is only allowed to be held by the authorization service and used to generate JWT encryption. Place the generated JKS file in the SRC /main/resource directory of the auth-service module.
- Generated for
JWT
decryptionThe public key
For resource services such as user-Service, the JWT needs to be decrypted using the JKS public key. To obtain the public key of the JKS file, run the following command:
$ keytool -list -rfc
--keystore fzp-jwt.jks | openssl x509
-inform pem
-pubkey
Copy the code
This command requires you to install the openSSL download address and then manually configure the directory where you installed OpenSSL.exe to the environment variable.
After entering the password fzp123, a lot of information is displayed. You only need to extract the PUBLIC KEY, as shown below:
—–BEGIN PUBLIC KEY—– MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlCFiWbZXIb5kwEaHjW+/ 7J4b+KzXZffRl5RJ9rAMgfRXHqGG8RM2Dlf95JwTXzerY6igUq7FVgFjnPbexVt3 vKKyjdy2gBuOaXqaYJEZSfuKCNN/WbOF8e7ny4fLMFilbhpzoqkSHiR+nAHLkYct OnOKMPK1SwmvkNMn3aTEJHhxGh1RlWbMAAQ+QLI2D7zCzQ7Uh3F+Kw0pd2gBYd8W +DKTn1Tprugdykirr6u0p66yK5f1T9O+LEaJa8FjtLF66siBdGRaNYMExNi21lJk i5dD3ViVBIVKi9ZaTsK9Sxa3dOX1aE5Zd5A9cPsBIZ12spYgemfj6DjOw6lk7jkG 9QIDAQAB —–END PUBLIC KEY—–
Cert file, copy the public key information above to the public. Cert file and save it. Put the file in the SRC /main/resources directory of the user-service resource service. Auth-service is set up.
- in
pom.xml
In the configurationjks
File suffix filter
Maven may compile JKS files during project compilation, resulting in garbled characters and unusable JKS files. You need to add the following to the POM.xml file:
<! -- Prevent JKS files from being compiled by Maven and become unusable.
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>cert</nonFilteredFileExtension>
<nonFilteredFileExtension>jks</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
Copy the code
- Finally, configure it on the startup class
@EnableEurekaClient
Comments Enable service registration.
@EnableEurekaClient
@SpringBootApplication
public class AuthServiceApplication {
public static void main(String[] args) { SpringApplication.run(AuthServiceApplication.class, args); }}Copy the code
6. Build the user-service resource service
- Create a new
user-service
Project module, completepom.xml
The file configuration is as follows:
<?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>io.github.ostenant.springcloud</groupId>
<artifactId>user-service</artifactId>
<version>0.0.1 - the 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>
Copy the code
- Modify the
user-service
Configuration file ofapplication.yml
, the configurationThe application name 为user-service
.The port number 为9090
. In addition, configuration is requiredfeign.hystrix.enable
为true
Open, that is,Feign
的Hystrix
Function. The complete configuration code is as follows:
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
Copy the code
- Configuring Resource Services
Inject a Bean of type JwtTokenStore, initialize the JWT converter JwtAccessTokenConverter, and set the public key used to decrypt JWT.
@Configuration
public class JwtConfig {
@Autowired
private JwtAccessTokenConverter jwtAccessTokenConverter;
@Bean
@Qualifier("tokenStore")
public TokenStore tokenStore(a) {
return new JwtTokenStore(jwtAccessTokenConverter);
}
@Bean
public JwtAccessTokenConverter jwtTokenEnhancer(a) {
// As a JWT converter
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);
}
// Set the public key
converter.setVerifierKey(publicKey);
returnconverter; }}Copy the code
Configure authentication management for resource services to authenticate all interfaces except those for registering and logging in.
@Configuration
@EnableResourceServer
public class ResourceServerConfig extends ResourceServerConfigurerAdapter{
@Autowired
private 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); }}Copy the code
Create a new configuration class GlobalMethodSecurityConfig, through @ EnableGlobalMethodSecurity annotation level security verification methods.
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class GlobalMethodSecurityConfig {}Copy the code
- Implement user registration interface
Copy the User, Role, and UserRepository classes of auth-Service to this module. Write a method to insert a user in the Service layer UserService as follows:
@Service
public class UserServiceDetail {
@Autowired
private UserRepository userRepository;
public User insertUser(String username,String password){
User user=new User();
user.setUsername(username);
user.setPassword(BPwdEncoderUtil.BCryptPassword(password));
returnuserRepository.save(user); }}Copy the code
Configure the utility class BPwdEncoderUtil for user password encryption:
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){
returnencoder.matches(rawPassword,encodedPassword); }}Copy the code
To implement a user registration API interface /user/register, the code is as follows:
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
UserServiceDetail userServiceDetail;
@PostMapping("/register")
public User postUser(@RequestParam("username") String username,
@RequestParam("password") String password){
returnuserServiceDetail.insertUser(username, password); }}Copy the code
- Implement user login interface
Add a login() method to the Service layer UserServiceDetail as follows:
@Service
public class UserServiceDetail {
@Autowired
private AuthServiceClient client;
public UserLoginDTO login(String username, String password) {
// Query the database
User user = userRepository.findByUsername(username);
if (user == null) {
throw new UserLoginException("error username");
}
if(! BPwdEncoderUtil.matches(password,user.getPassword())){throw new UserLoginException("error password");
}
// Get the JWT from auth-service
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);
returnuserLoginDTO; }}Copy the code
AuthServiceClient, as a Feign Client, obtains JWT by remotely calling auth-Service service interface/OAuth/Token. In the request /oauth/token API interface, the Authorization information, authentication type (grant_type), username (username), and password (password) need to be passed in the request header. The codes are as follows:
@FeignClient(value = "auth-service", fallback = AuthServiceHystrix.class)
public interface AuthServiceClient {
@PostMapping("/oauth/token")
JWT getToken(@RequestHeader("Authorization") String authorization,
@RequestParam("grant_type") String type,
@RequestParam("username") String username,
@RequestParam("password") String password);
}
Copy the code
AuthServiceHystrix is the fuse of AuthServiceClient. The code is as follows:
@Component
public class AuthServiceHystrix implements AuthServiceClient {
private static final Logger LOGGER = LoggerFactory.getLogger(AuthServiceHystrix.class);
@Override
public JWT getToken(String authorization, String type, String username, String password) {
LOGGER.warn("Fallback of getToken is executed")
return null; }}Copy the code
JWT contains information such as access_token, token_type, and refresh_token as follows:
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;
public String getAccess_token(a) {
return access_token;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
public String getToken_type(a) {
return token_type;
}
public void setToken_type(String token_type) {
this.token_type = token_type;
}
public String getRefresh_token(a) {
return refresh_token;
}
public void setRefresh_token(String refresh_token) {
this.refresh_token = refresh_token;
}
public int getExpires_in(a) {
return expires_in;
}
public void setExpires_in(int expires_in) {
this.expires_in = expires_in;
}
public String getScope(a) {
return scope;
}
public void setScope(String scope) {
this.scope = scope;
}
public String getJti(a) {
return jti;
}
public void setJti(String jti) {
this.jti = jti; }}Copy the code
UserLoginDTO contains a User and a JWT member attribute that is used to return data to the entity:
public class UserLoginDTO {
private JWT jwt;
private User user;
public JWT getJwt(a) {
return jwt;
}
public void setJwt(JWT jwt) {
this.jwt = jwt;
}
public User getUser(a) {
return user;
}
public void setUser(User user) {
this.user = user; }}Copy the code
Login exception class UserLoginException
public class UserLoginException extends RuntimeException {
public UserLoginException(String message) {
super(message); }}Copy the code
Global exception handling section class ExceptionHandle
@ControllerAdvice
@ResponseBody
public class ExceptionHandler {
@ExceptionHandler(UserLoginException.class)
public ResponseEntity<String> handleException(Exception e) {
return newResponseEntity(e.getMessage(), HttpStatus.OK); }}Copy the code
Add a new API interface /user/login to the UserController class of the Web layer as follows:
@PostMapping("/login")
public UserLoginDTO login(@RequestParam("username") String username,
@RequestParam("password") String password) {
return userServiceDetail.login(username,password);
}
Copy the code
- In order to testUser permissionsAnd add another one
/foo
Interface, which is requiredROLE_ADMIN
Permission to access.
@RequestMapping(value = "/foo", method = RequestMethod.GET)
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public String getFoo(a) {
return "i'm foo, " + UUID.randomUUID().toString();
}
Copy the code
- Finally, use annotations on the application’s startup class
@EnableFeignClients
openFeign
Function can be.
@SpringBootApplication
@EnableFeignClients
@EnableEurekaClient
public class UserServiceApplication {
public static void main(String[] args) { SpringApplication.run(UserServiceApplication.class, args); }}Copy the code
Start eureka-service, auth-service, and user-service in sequence.
7. Use Postman tests
- To register a user, return the registration success message
- Log in using the user name and password
JWT
- Copy the one above
access_token
到header
Header, request requiredUser permissions 的/user/foo
interface
"Authorization": "Bearer {access_token}"
Copy the code
Access denied because there is no permission. Manually add the ROLE_ADMIN permission to the database and associate it with the user. Log in again and get the JWT, requesting the /user/foo interface again.
conclusion
In this case, the user accesses the JWT encrypted by the authorization service through the login interface. After the user successfully obtains the JWT, the user needs to carry the JWT in every subsequent request to access the resource service. The resource service decrypts the JWT through the public key. After the decryption is successful, the resource service can obtain the user information and permission information, so as to determine who the user corresponding to the JWT is and what permission it has.
- Advantages:
After obtaining a Token once and using it multiple times, the resource service does not access the user information and user permission information corresponding to the Token each time.
- Disadvantages:
If the user information or permission information is changed, the information stored in the Token remains unchanged. You need to log in to the Token again to obtain the new Token. Even if the Token is obtained again, if the original Token has not expired, it can still be used. One improvement is to cache the acquired Token on the gateway after a successful login. If the user’s permission changes, the Token cached on the gateway is deleted. When the request passes through the gateway, the system checks whether the requested Token exists in the cache. If the requested Token does not exist in the cache, the system prompts the user to log in again.
reference
- An In-depth Understanding of Spring Cloud and Microservice Construction by Zhipeng Fang
Welcome to pay attention to the technical public number: Zero one Technology Stack
This account will continue to share learning materials and articles on back-end technologies, including virtual machine basics, multithreaded programming, high-performance frameworks, asynchronous, caching and messaging middleware, distributed and microservices, architecture learning and progression.