本次基于Spring Boot
整合了Spring Security
和Jwt
,可以解决前后端分离之后用户认证与授权的问题。在前后端还未分离的时候,对用户进行身份认证大约是这样的。
这种缺点就是身份信息需要客户端和服务器同时存储,当用户基数很大的时候,需要大量的内存来解决这个问题。
在前后端分离之后,基于token
的用户身份认证大约是这样的。
这种好处是token
只需要存储到客户端,服务端只需要对发来的请求中验证token的有效性。
本次便使用基于token
的方式,结合spring security
进行一次简单的身份认证与授权。
相关版本信息
名称 | 版本 |
---|---|
IDEA商业版 | 2020.1 |
JDK | JDK1.8 |
Maven | 3.5.4 |
Windows | 家庭版1903 |
项目结构
.
├── .idea
├── src
│ └── main
| ├── java
| | └── com
| | └── example
| | ├── controller
| | | └── HelloResource.java
| | ├── filters
| | | └── JwtRequestFilter.java
| | ├── model
| | | ├── AuthenticationRequest.java
| | | └── AuthenticationResponse.java
| | ├── security
| | | ├── MyUserDetailsService.java
| | | └── SecurityConfigurer.java
| | ├── utils
| | | └── JwtUtil.java
| | └── Application.java
│ └── resources
│ └── application.properties
├── test
├── target
├── pom.xml
└── security-jwt.iml
在pom.xml添加相关jar包
<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>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.0</version>
</dependency>
创建Application.java
这个其实就是Spring Boot
的入口文件,名称不一样也没事,内容也没有改动。
package com.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
创建SecurityConfigurer.java
这个类是Spring Security
的配置类,Spring Boot
提倡去掉配置文件,用配置类来代替,道理都差不多,我还是熟悉xml
一些。
package com.example.security;
import com.example.filters.JwtRequestFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService myUserDetailsService;
@Autowired
JwtRequestFilter jwtRequestFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService).passwordEncoder(bCryptpasswordEncoder());
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/authenticate")
.permitAll()
.anyRequest()
.authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Bean
public BCryptPasswordEncoder bCryptpasswordEncoder() {
return new BCryptPasswordEncoder();
}
}
创建MyUserDetailsService.java
这个类是通过传来用户的username
,返回一个用户对象,这里为了简便没有从数据库进行查询,以后改成从数据库访问用户信息,直接在这里查询并返回一个用户就行了。
这里密码采用了BCR
加密。
package com.example.security;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@Service
public class MyUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
return new User("foo",new BCryptPasswordEncoder().encode("foo"),new ArrayList<>());
}
}
创建JwtUtil.java
这个是Jwt
的配置类,可以配置token
的SECRET_KEY
,到期时间等等,更重要的作用是可以生成一个token
。
package com.example.utils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
private String SECRET_KEY = "secret";
public String extractUsername(String token){
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token){
return extractClaim(token,Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims,T> claimsResolver){
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
public Claims extractAllClaims(String token){
return Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
}
public Boolean isTokenExpired(String token){
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails){
Map<String,Object> claims = new HashMap<>();
return createToken(claims,userDetails.getUsername());
}
private String createToken(Map<String,Object> claims,String subject){
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis()+100*60*60*10))
.signWith(SignatureAlgorithm.HS256,SECRET_KEY)
.compact();
}
public Boolean validateToken(String token,UserDetails userDetails){
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername())) && (!isTokenExpired(token));
}
}
说明
本文章原创自我的个人博客 https://srcrs.top ,如需要完整文章内容,以及源码,请访问:https://srcrs.top/posts/202007241.html ,迫不得已引流方式请勿介意。