当前位置:网站首页>Kill session? This cross domain authentication solution is really elegant
Kill session? This cross domain authentication solution is really elegant
2022-06-12 00:40:00 【Silent King II】
User login authentication is Web A very common business in applications , The general process is like this :
- The client sends the user name and password to the server
- After the server-side verification passes , In the current session (session) Save relevant data in , For example, login time 、 Sign in IP etc. .
- The server returns a... To the client session_id, The client saves it in Cookie in .
- When the client sends a request to the server again , take session_id Send it back to the server .
- On the server side session_id after , Identify the user .
In the case of a single machine , There is no problem with this model , But for front and rear end separation Web applications , It's very painful . So there is another solution , The server side will no longer save session data , Instead, save it on the client , Each time the client initiates a request, it sends the data to the server for verification .JWT(JSON Web Token) It is a typical representative of this scheme .

One 、 About JWT
JWT, It's the most popular Cross domain Certification solutions : The client initiates a user login request , After the server receives and authenticates successfully , Generate a JSON object ( As shown below ), And then it's returned to the client .
{
"sub": "wanger",
"created": 1645700436900,
"exp": 1646305236
}
When the client communicates with the server again , Put this JSON Take the object with you , As a certificate of mutual trust between the front and back ends . After the server receives the request , adopt JSON Object to authenticate the user , This eliminates the need to save any session Data. .
If I use a user name now wanger And password 123456 Access programming meow (Codingmore) Of login Interface , So practical JWT It's a string that looks like it's encrypted .

In order to let everyone see more clearly , I copied it to jwt Its official website .

left Encoded Part is JWT Ciphertext , Intermediate use 「.」 Divided into three parts ( On the right side Decoded part ):
- Header( Head ), describe JWT Metadata , among
algThe algorithm of attribute representation signature ( At present, it is HS512); - Payload( load ), It is used to store the data that needs to be transferred , among
subProperty represents the subject ( The actual value is user name ),createdAttribute representation JWT Time of birth ,expProperty indicates the expiration time - Signature( Signature ), Signature of the first two parts , Prevent data tampering ; You need to specify a key on the server side ( Only the server side knows ), Can't leak to client , And then use Header The signature algorithm specified in , Follow the formula below to generate a signature :
HMACSHA512(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
your-256-bit-secret
)
After you figure out the signature , And then Header、Payload、Signature Concatenate into a string , Intermediate use 「.」 Division , You can return it to the client .
The client gets JWT after , Can be placed in localStorage, It can also be placed in Cookie Inside .
const TokenKey = '1D596CD8-8A20-4CEC-98DD-CDC12282D65C' // createUuid()
export function getToken () {
return Cookies.get(TokenKey)
}
export function setToken (token) {
return Cookies.set(TokenKey, token)
}
When the client communicates with the server in the future , Just take this JWT, Generally placed on HTTP The header of the request Authorization In the field .
Authorization: Bearer <token>

After the server receives the request , Right again JWT To verify , If the verification is passed, the corresponding resources will be returned .
Two 、 actual combat JWT
First step , stay pom.xml Add... To the file JWT Dependence .
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
The second step , stay application.yml Add JWT Configuration item for .
jwt:
tokenHeader: Authorization #JWT Stored request header
secret: codingmore-admin-secret #JWT The key used for encryption and decryption
expiration: 604800 #JWT Beyond the deadline of (60*60*24*7)
tokenHead: 'Bearer ' #JWT Get the beginning of the load
The third step , newly build JwtTokenUtil.java Tool class , There are three main ways :
generateToken(UserDetails userDetails): Generated according to the logged in user tokengetUserNameFromToken(String token): from token Get login uservalidateToken(String token, UserDetails userDetails): Judge token Whether it is still valid
public class JwtTokenUtil {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private Long expiration;
@Value("${jwt.tokenHead}")
private String tokenHead;
/** * According to the user information token */
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED, new Date());
return generateToken(claims);
}
/** * According to the user name 、 Create time generation JWT Of token */
private String generateToken(Map<String, Object> claims) {
return Jwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
/** * from token Get login user name */
public String getUserNameFromToken(String token) {
String username = null;
Claims claims = getClaimsFromToken(token);
if (claims != null) {
username = claims.getSubject();
}
return username;
}
/** * from token In order to get JWT Load in */
private Claims getClaimsFromToken(String token) {
Claims claims = null;
try {
claims = Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
} catch (Exception e) {
LOGGER.info("JWT Format validation failed :{}", token);
}
return claims;
}
/** * verification token Is it still valid * * @param token From the client token * @param userDetails User information from the database */
public boolean validateToken(String token, UserDetails userDetails) {
String username = getUserNameFromToken(token);
return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
}
/** * Judge token Is it invalid */
private boolean isTokenExpired(String token) {
Date expiredDate = getExpiredDateFromToken(token);
return expiredDate.before(new Date());
}
/** * from token Get the expiration time in the */
private Date getExpiredDateFromToken(String token) {
Claims claims = getClaimsFromToken(token);
return claims.getExpiration();
}
}
Step four , stay UsersController.java Newly added login Login interface , Receive user name and password , And will JWT Return to the client .
@Controller
@Api(tags=" user ")
@RequestMapping("/users")
public class UsersController {
@Autowired
private IUsersService usersService;
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;
@ApiOperation(value = " Log in and return to token")
@RequestMapping(value = "/login", method = RequestMethod.POST)
@ResponseBody
public ResultObject login(@Validated UsersLoginParam users, BindingResult result) {
String token = usersService.login(users.getUserLogin(), users.getUserPass());
if (token == null) {
return ResultObject.validateFailed(" Wrong user name or password ");
}
// take JWT Pass back to the client
Map<String, String> tokenMap = new HashMap<>();
tokenMap.put("token", token);
tokenMap.put("tokenHead", tokenHead);
return ResultObject.success(tokenMap);
}
}
Step five , stay UsersServiceImpl.java Newly added login Method , Query the user from the database according to the user name , Generate after password verification JWT.
@Service
public class UsersServiceImpl extends ServiceImpl<UsersMapper, Users> implements IUsersService {
@Autowired
private PasswordEncoder passwordEncoder;
@Autowired
private JwtTokenUtil jwtTokenUtil;
public String login(String username, String password) {
String token = null;
// The password needs to be passed after being encrypted by the client
try {
// Query the user + User resources
UserDetails userDetails = loadUserByUsername(username);
// Verify password
if (!passwordEncoder.matches(password, userDetails.getPassword())) {
Asserts.fail(" Incorrect password ");
}
// return JWT
token = jwtTokenUtil.generateToken(userDetails);
} catch (AuthenticationException e) {
LOGGER.warn(" Login exception :{}", e.getMessage());
}
return token;
}
}
Step six , newly added JwtAuthenticationTokenFilter.java, Every time the client makes a request, it will respond to JWT To verify .
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
@Autowired
private UserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Value("${jwt.tokenHeader}")
private String tokenHeader;
@Value("${jwt.tokenHead}")
private String tokenHead;
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws ServletException, IOException {
// Get... From the client request JWT
String authHeader = request.getHeader(this.tokenHeader);
// The JWT It's the format we stipulated , With tokenHead start
if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
// The part after "Bearer "
String authToken = authHeader.substring(this.tokenHead.length());
// from JWT Get user name
String username = jwtTokenUtil.getUserNameFromToken(authToken);
LOGGER.info("checking username:{}", username);
// SecurityContextHolder yes SpringSecurity A tool class of
// Save the security context of the current user in the application
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
// Obtain login user information according to user name
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
// verification token Is it overdue
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
// Save the logged in user in a security context
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails,
null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
LOGGER.info("authenticated user:{}", username);
}
}
}
chain.doFilter(request, response);
}
}
JwtAuthenticationTokenFilter Inherited OncePerRequestFilter, This filter ensures that a request passes only once filter, It doesn't need to be repeated . in other words , Every time the client initiates a request , The filter will execute once .
This filter is very critical , Basically, I added comments to every line of code , Yes, of course , To make sure everyone can figure out what this class does , Let me draw another flow chart , So it's clear .

SpringSecurity Is a security management framework , You can talk to Spring Boot Application seamless connection ,SecurityContextHolder Is one of the key tool classes , Hold security context information , Who is the user who saves the current operation , Whether the user has been authenticated , Key information such as permissions owned by users .
SecurityContextHolder Default used ThreadLocal Policies to store authentication information ,ThreadLocal It's characterized by the data in it , Which thread has stored , Which thread can access . This means that after different requests enter the server , There will be different Thread To deal with , For example, threads A The request 1 User information is stored in ThreadLocal, Threads B Processing request 2 You can't get the user's information when you are .
So JwtAuthenticationTokenFilter The filter will do it every time a request comes JWT Validation of the , Ensure that the request from the client is secure . then SpringSecurity The next request interface will be released . This is also JWT and Session Fundamental difference :
- JWT You need to verify every request , And as long as JWT No expired , Even if the server is restarted , The certification is still valid .
- Session Without expiration, it is not necessary to re verify the user information , When the server is restarted , The user needs to log in again to get a new Session.
in other words , stay JWT Under the scheme of , The key saved on the server side (secret) We must not divulge , Otherwise, the client can forge the user's authentication information according to the signature algorithm .
3、 ... and 、Swagger Add JWT verification
For back-end developers , How to be in Swagger( Integrated Knife4j Beautify ) Add JWT What about verification ?
First step , visit login Interface , Enter your user name and password to log in , Get the information returned by the server JWT.

The second step , Collect the data returned by the server tokenHead and token, Fill it in Authorize( Be careful tokenHead and token There is a space between ) Complete login authentication .

The third step , When another interface is requested ,Swagger Will automatically Authorization Send it to the server as request header information .

Step four , After the server receives the request , Will pass JwtAuthenticationTokenFilter Filter right JWT check .

Only this and nothing more , The whole process has been opened up , perfect !
Four 、 summary
To sum up , use JWT To solve the cross domain authentication in the front and rear end separation project is very smooth , This is mainly due to JSON The generality of , Can cross language ,JavaScript and Java All support ; in addition ,JWT The composition of is very simple , Very easy to transmit ; also JWT There is no need to save session information on the server side (Session), Very easy to expand .
Yes, of course , In order to ensure JWT The security of , Not in JWT Save sensitive information in , Because once the private key is compromised ,JWT It is easy to decrypt on the client ; If possible , Please use HTTPS agreement .
Reference link :
Ruan Yifeng :https://www.ruanyifeng.com/blog/2018/07/json_web_token-tutorial.html
Spring, summer, autumn and winter :https://segmentfault.com/a/1190000012557493
A little rain in Jiangnan :https://cloud.tencent.com/developer/article/1612175
Dearmadman:https://www.jianshu.com/p/576dbf44b2ae
mcarozheng:http://www.macrozheng.com/
Source path :
This article has been included in GitHub On the star 1.6k+ star Open source column 《Java The road to advancement of programmers 》, It is said that every excellent Java Programmers like her , Humor and wit humor 、 Easy to understand . The content includes Java Basics 、Java Concurrent programming 、Java virtual machine 、Java Enterprise development 、Java Interview and other core knowledge points . learn Java, Just recognize it Java The road to advancement of programmers .
https://github.com/itwanger/toBeBetterJavaer
star Having this warehouse means that you have become an excellent Java The potential of Engineers . You can also stamp the link below to jump to 《Java The road to advancement of programmers 》 The official website of , Start a pleasant learning journey .

Nothing makes me stay —— Except for the purpose , Even if there are roses by the shore 、 There's shade 、 There is a peaceful harbor , I'm not a boat .
边栏推荐
- Lambda快速入门
- Lambda中间操作filter
- 多年测试菜鸟对黑盒测试的理解
- DevOps落地实践点滴和踩坑记录-(1)
- Matplotlip basic drawing learning of data analysis - 01
- How to uninstall pscs6 in win10 system
- Win jar package setting boot auto start
- Point cloud library PCL from introduction to mastery learning records Chapter 8
- QApplication a (argc, argv) and exec() in the main function of QT getting started
- Industrial control system ICs
猜你喜欢

C语言练习:ESP32 BLE低功耗蓝牙服务端数据打包和客户端数据解析

Share an open source, free and powerful video player library

C language exercise: esp32 ble Low Power Bluetooth server data packaging and client data analysis

How to uninstall pscs6 in win10 system

LabVIEW Arduino电子称重系统(项目篇—1)

Argodb 3.2 of star ring technology was officially released to comprehensively upgrade ease of use, performance and security

Exploration of qunar risk control safety products

R language spline curve piecewise linear regression model piecewise regression estimation of individual stock beta value analysis of yield data

组态王如何利用无线Host-Link通信模块远程采集PLC数据?

What is bonded warehouse and what is the difference between them
随机推荐
730.Count Different Palindromic Subsequences
WPS标题段前间距设置无效解决方案
Tencent programmer roast: 1kW real estate +1kw stock +300w cash, ready to retire at the age of 35
System.CommandLine选项Option
Openmmlab:ai CV framework
Do you want to take the postgraduate entrance examination? Will you be able to find a good job after graduate school?
在玻璃上构建电路
How to optimize plantuml flow diagram (sequence diagram)
Lambda中间操作sorted
The latest report of Xinsi technology shows that 97% of applications have vulnerabilities
【SignalR全套系列】之在.Net6中实现SignalR分组通信
Enterprise wechat H5_ Integrated message decryption class, message push get and post callback processing
Breadth first search depth first search dynamic programming leetcode topic: delivering information
The "hard words" about interface testing
C language pointer and array - learning 23
Construction environnementale 2
Creating and running JMeter performance test scenarios
Mysql database: introduction to database 𞓜 addition, deletion, modification and query
[case] building a universal data lake for Fuguo fund based on star ring technology data cloud platform TDC
How to send Apple phone WPS files to QQ mailbox