(Transfer + share) Spring Security + JWT front-end and back-end separation architecture implementation / Chen Jiqiang 90 07-24 15:37

Task case analysis

In our traditional B\S application development method, session is used for state management, for example: save login, user, permission and other state information. The principle of this method is roughly as follows:

The above is a stateful service.

Of course, during this whole process, cookies and sessionid are automatically maintained by the server and the browser. Therefore, it is not perceptible from the coding level, and programmers can only perceive the access of session data. However, this method is not applicable in some cases. For example: non-browser clients, mobile phones, etc., because they do not have the function of automatically maintaining cookies in the browser. For example: a cluster application, the same application is deployed on three hosts of A, B, and C to implement load balancing applications, one of which can be loaded when the other is down. To know that the session is stored in the server memory, the three hosts must have different memory. Then you visit A ​​when you log in, and you visit B when you get interface data, you cannot guarantee the uniqueness and sharing of the session. Of course, we have solutions for the above situations (such as redis sharing session, etc.), and you can continue to use session to save state. But there is another way to do without session, that is, to develop a stateless application, JWT is such a solution.

What is the statelessness mentioned above?

Each service in the microservice cluster uses a RESTful-style interface provided externally. And one of the most important specifications of the RESTful style is: the statelessness of the service, that is: the server does not store any client requester information, and each request of the client must have self-descriptive information, and the identity of the client can be identified through this information. What are the benefits of this statelessness? Client requests do not rely on server information, multiple requests do not need to access the same server server cluster and state, transparent to the client, the server can be migrated and scaled arbitrarily (it can be easily deployed in clusters) to reduce the server Store pressure.

How to realize the process of stateless login:

First, the client sends the account name/password to the server for authentication. After the authentication is passed, the server encrypts and encodes the user information into a token. After returning to the client, every time the client sends a request, it needs to carry the authentication token server pair. The token sent by the client is decrypted, judged whether it is valid, and the user login information is obtained

In the front-end separation project, there are many login strategies, but JWT is currently a more popular solution. This article will share with you how to use SpringSecurity and JWT together to realize the separation of the front-end and the back-end. Login solution.

What is JWT?

JWT, the full name is Json Web Token, is a JSON-style lightweight authorization and identity authentication specification that can realize stateless, distributed Web application authorization. JWT is an encrypted interface access password, and the password contains user name information. So you can know who you are? Can you also know if you can access the app?

JWT interaction flow chart:

Authentication code implementation:

1. Introduce jwt toolkit in pom.xml

<!--Token生成与解析-->

<dependency>

<groupId>io.jsonwebtoken</groupId>

<artifactId>jjwt</artifactId>

<version>0.9.0</version>

</dependency>

2. Add the following custom configurations about JWT in application.yml

# tokenConfigurationtoken:# Token custom identification header: Authorization# Token secret key# secret: abcdefghijklmnopqrstuvwxyzsecret: (EMOK:)_$^11244^%$_(IS:)_@@++--(COOL: )_++++_.sds_(GUY:)# Token validity period (default 30 minutes) expireTime: 3600000

The header is the name of the HTTP header carrying the JWT token. Although I call it Authorization here, the less readable it is, the safer it is in actual production. The secret is the key used to encrypt and decrypt the basic information of the JWT. Although I have written the configuration file dead here, it is usually not written directly in the configuration file in actual production. Instead, it is passed through the startup parameters of the application and needs to be modified periodically. The expiration is the effective time of the JWT token.

3. Write the JwtLoginController class

@RestController

public class JwtLoginController {

@Autowired

JwtAuthService jwtAuthService;

/** * 登录方法

*

* @param username 用户名

* @param password 密码

* @return 结果

*/

@PostMapping({"/login", "/"})

public RestResult login(String username, String password) {

RestResult result = RestResult.success();

String token = jwtAuthService.login(username, password);

result.put("token", token);

return result;

}

}

4. Write the JwtAuthService class

@Service

public class JwtAuthService {

@Resource

private AuthenticationManager authenticationManager;

@Resource

private jwtTokenUtil jwtTokenUtil;

/**

* 登录认证换取JWT令牌

* @param username 用户名

* @param password 密码

* @return 结果

*/

public String login(String username, String password) {

// 用户验证

Authentication authentication = null;

try {

// 该方法会去调用UserDetailsServiceImpl.loadUserByUsername authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));

} catch (Exception e) {

throw new RuntimeException("用户名密码错误");

}

Users loginUser = (Users) authentication.getPrincipal();

// 生成token

return jwtTokenUtil.generateToken(loginUser);

}

}

5. Write tool classes for JWT token generation and refresh

@Data

@Component

public class jwtTokenUtil {

@Value("${token.secret}")

private String secret;

@Value("${token.expireTime}")

private Long expiration;

@Value("${token.header}")

private String header;

/** * 生成token令牌

* @param userDetails 用户

* @return 令token牌

*/

public String generateToken(UserDetails userDetails) {

Map<String, Object> claims = new HashMap<>(2);

claims.put("sub", userDetails.getUsername());

claims.put("created", new Date());

return generateToken(claims);

}

/**

* 从令牌中获取用户名

* @param token 令牌

* @return 用户名

*/

public String getUsernameFromToken(String token) {

String username;

try {

Claims claims = getClaimsFromToken(token);

username = claims.getSubject();

} catch (Exception e) {

username = null;

}

return username;

}

/**

* 判断令牌是否过期

* @param token 令牌

* @return 是否过期

*/

public Boolean isTokenExpired(String token) {

try {

Claims claims = getClaimsFromToken(token);

Date expiration = claims.getExpiration();

return expiration.before(new Date());

} catch (Exception e) {

return false;

}

}

/**

* 刷新令牌

*

* @param token 原令牌

* @return 新令牌

*/

public String refreshToken(String token) {

String refreshedToken;

try {

Claims claims = getClaimsFromToken(token);

claims.put("created", new Date());

refreshedToken = generateToken(claims);

} catch (Exception e) {

refreshedToken = null;

}

return refreshedToken;

}

/**

* 验证令牌

*

* @param token 令牌

* @param userDetails 用户

* @return 是否有效

*/

public Boolean validateToken(String token, UserDetails userDetails) {

String username = getUsernameFromToken(token);

return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));

}

/**

* 从claims生成令牌,如果看不懂就看谁调用它

*

* @param claims 数据声明

* @return 令牌

*/

private String generateToken(Map<String, Object> claims) {

Date expirationDate = new Date(System.currentTimeMillis() + expiration); return Jwts.builder().setClaims(claims) .setExpiration(expirationDate)

.signWith(SignatureAlgorithm.HS512, secret)

.compact();

}

/**

* 从令牌中获取数据声明,如果看不懂就看谁调用它

*

* @param token 令牌

* @return 数据声明

*/

private Claims getClaimsFromToken(String token) {

Claims claims;

try {

claims =Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } catch (Exception e) {

claims = null;

}

return claims;

}}

Authentication code implementation:

6. Write JwtAuthTokenFilter to complete authentication

@Component

public class JwtAuthTokenFilter extends OncePerRequestFilter {

@Resource

private jwtTokenUtil jwtTokenUtil;

@Resource

UserDetailsService myUserDetailsService;

@Override

protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

String jwtToken = request.getHeader(jwtTokenUtil.getHeader()); if(!StringUtils.isEmpty(jwtToken)){

String username = jwtTokenUtil.getUsernameFromToken(jwtToken); //如果可以正确的从JWT中提取用户信息,并且该用户未被授权 if(username != null && SecurityContextHolder.getContext().getAuthentication() == null){ UserDetails userDetails = myUserDetailsService.loadUserByUsername(username); if(jwtTokenUtil.validateToken(jwtToken,userDetails)){

//给使用该JWT令牌的用户进行授权 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());

//交给spring security管理,在之后的过滤器中不会再被拦截进行二次授权了 SecurityContextHolder.getContext().setAuthentication(authenticationToken); }

}

}

filterChain.doFilter(request,response);

} }

7. Modify the SecurityConfig.configure method to configure stateless and JWT filter filters

@Override

protected void configure(HttpSecurity httpSecurity) throws Exception {

httpSecurity

// Authentication failure handling class //.exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and() // Based on token, so session is not needed. SessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

//Configure permissions

.authorizeRequests()

// For the login verification code captchaImage, anonymous access is allowed. antMatchers("/login").anonymous()

.antMatchers(

HttpMethod.GET,

"/*.html",

"/**/*.html",

"/**/*.css",

"/**/*.js"

) .permitAll ()

.antMatchers("/order")

//The resource path that needs to be exposed

.hasAnyAuthority("ROLE_USER", "ROLE_ADMIN")

//User role and admin role can access

.antMatchers("/system/user", "/system/role", "/system/menu") .hasAnyRole("ADMIN") //admin role can access

// All requests except the above require authentication. anyRequest().authenticated().and()//authenticated() requires that you have logged in to the application when executing the request

// CRSF is disabled because session is not used

.csrf().disable() ;

//Disable cross-site csrf attack defense, otherwise you will not be able to log in successfully

//Logout function

httpSecurity.logout().logoutUrl("/logout");

// Add JWT filter

httpSecurity.addFilterBefore(jwtAuthTokenFilter, UsernamePasswordAuthenticationFilter.class);

}

8. Use postman for testing

Next, we access an interface "/system/user" that we defined, and the result is that access is forbidden.

Enter username: alex, password: 123456, click the send button:

When we pass the token returned in the previous step to the header, we can respond to the hello interface result normally.

 

                                     ------------------------------------------- < END >---------------------------------------

 

https://mbd.baidu.com/newspage/data/landingshare?pageType=1&isBdboxFrom=1&context=%7B%22nid%22%3A%22news_8933629595806296360%22%2C%22sourceFrom%22%3A%22bjh%22%7D

Guess you like

Origin blog.csdn.net/qq_31653405/article/details/107606261