Article directory
-
- 3. Principle analysis
- 4. Session management
- 5. Integrated authentication and authorization in RBAC
- 6. JWT
- 7. Appendix: HttpSecurity configuration items
3. Principle analysis
3.1 Structural analysis
The problem that Spring Security solves is security access control , and the security access control function is actually to intercept all requests entering the system and verify whether each request can access the resources it expects. According to the previous knowledge learning, it can be achieved through technologies such as Filter or AOP. Spring Security's protection of Web resources is achieved by Filter, so start with this Filter and gradually deepen the principles of Spring Security.
When Spring Security is initialized, a Servlet filter named SpringSecurityFilterChain will be created. The type is org.springframework.security.web.FilterChainProxy. It implements javax.servlet.Filter, so external requests will go through this class. The figure below is Spring Security filter chain structure diagram:
The implementation of Spring Security functions is mainly completed by a series of filter chains cooperating with each other.
The following introduces the main filters in the filter chain and their functions:
SecurityContextPersistenceFilter This Filter is the entrance and exit of the entire interception process (that is, the first and last interceptor). It will obtain the SecurityContext from the configured SecurityContextRepository when the request starts, and then set it to the SecurityContextHolder. After the request is completed, the SecurityContext held by the SecurityContextHolder is saved to the configured SecurityContextRepository, and the SecurityContext held by the securityContextHolder is cleared at the same time;
UsernamePasswordAuthenticationFilter is used to handle authentication from form submissions. The form must provide the corresponding user name and password. It also contains AuthenticationSuccessHandler and AuthenticationFailureHandler for processing after successful or failed login. These can be changed according to needs;
FilterSecurityInterceptor is used to protect web resources and uses AccessDecisionManager to authorize access to the current user;
ExceptionTranslationFilter can catch all exceptions from FilterChain and handle them. But it will only handle two types of exceptions: AuthenticationException and AccessDeniedException, and it will continue to throw other exceptions.
3.1 Login authentication process analysis
Let’s analyze the certification process in detail:
-
The user's submitted user name and password are obtained by the UsernamePasswordAuthenticationFilter filter in the SecurityFilterChain, and encapsulated as a request for Authentication, usually the implementation class UsernamePasswordAuthenticationToken.
-
The filter then submits the Authentication to the authentication manager (AuthenticationManager) for authentication
-
After successful authentication, the AuthenticationManager identity manager returns an Authentication instance filled with information (including the permission information, identity information, and details mentioned above, but the password is usually removed).
-
The SecurityContextHolder security context container sets the Authentication filled with information in step 3 through the SecurityContextHolder.getContext().setAuthentication(…) method. It can be seen that the AuthenticationManager interface (authentication manager) is the core interface related to authentication and the starting point for initiating authentication. Its implementation class is ProviderManager. Spring Security supports multiple authentication methods, so ProviderManager maintains a List to store multiple authentication methods. In the end, the actual authentication work is completed by AuthenticationProvider. We know that the corresponding AuthenticationProvider implementation class of the web form is DaoAuthenticationProvider, which maintains a UserDetailsService internally and is responsible for obtaining UserDetails. Finally, the AuthenticationProvider populates the UserDetails into the Authentication. The general relationship between the core components of authentication is as follows:
Simple diagram of flow chart:
3.1.1 UserDetailsService
In the analysis process just now, we saw that DaoAuthenticationProvider calls UserDetailsService to query data and then compare it. The role of UserDetailsService in the entire authentication process is only responsible for querying data. Whether to query memory data or database data is determined by our own configuration. For comparison The operation is done internally by DaoAuthenticationProvider.
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
3.1.2 Customize UserDetailsService
Original configuration:
@Bean
public UserDetailsService userDetailsService() {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
manager.createUser(User.withUsername("lisi").password("456").authorities("p2").build());
return manager;
}
In our previous configuration, we queried data in memory, but in actual project development, we queried the database.
Custom UserDetailsService operation
@Service
public class SpringDataUserDetailsService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//登录账号
System.out.println("username=" + username);
// 根据账号去数据库查询...
// 这里暂时使用静态数据
UserDetails userDetails = User.withUsername(username).password("123").
authorities("p1").build();
return userDetails;
}
}
Restart the project, request authentication, and the loadUserByUsername method of SpringDataUserDetailsService is called to query user information.
3.1.3 PasswordEncoder
Get to know PasswordEncoder:
After the DaoAuthenticationProvider authentication processor obtains UserDetails through UserDetailsService, how does it interact with the request?
How about comparing passwords in Authentication?
Here, Spring Security has made an abstraction to adapt to a variety of encryption types. DaoAuthenticationProvider compares passwords through the matches method of the PasswordEncoder interface, and the specific password comparison details depend on the implementation:
public interface PasswordEncoder {
String encode(CharSequence var1);
boolean matches(CharSequence var1, String var2);
default boolean upgradeEncoding(String encodedPassword) {
return false;
}
}
Spring Security provides many built-in PasswordEncoders, which can be used out of the box. To use a certain PasswordEncoder, you only need to do the following:
Just make the following statement, as follows:
@Bean
public PasswordEncoder passwordEncoder() {
return NoOpPasswordEncoder.getInstance();
}
NoOpPasswordEncoder uses a string matching method and does not perform encryption and comparison processing on passwords.
In actual projects, it is recommended to use BCryptPasswordEncoder, Pbkdf2PasswordEncoder, SCryptPasswordEncoder, etc. If you are interested, you can take a look at the specific implementation of these PasswordEncoder.
Defined in the security configuration class:
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
The test found : Encoded password does not look like BCrypt
Reason : The password in the database is in clear text. After the password passed from the front desk is encrypted and compared, it is inconsistent.
Use BCrypt to encrypt passwords
1. Confidentiality and verification of passwords
@org.junit.Test
public void test(){
String gensalt = BCrypt.gensalt();
System.out.println(gensalt);
String password = BCrypt.hashpw("123",gensalt );
System.out.println(password);
boolean checkpw = BCrypt.checkpw("123", "$2a$10$XeDXzobQ32ExDoZ1XNh1DOvAxJFtZgwwM1njc.vOzeYRFHyYPv1ay");
System.out.println(checkpw);
}
2. Modify the password format in the configuration class:
UserDetails userDetails = User.withUsername(username).password("$2a$10$m44lS0/w2yRIuFMzUIRJ9OFUq9HMaLm2eqkSlKdfASpyZJgYrGe2.").
authorities("p1").build();
Note: The password stored in the actual project is in ciphertext.
3.2 Authorization process analysis
3.2.1 Principle analysis of configuration method
Flowchart :
Through a quick start , we know that Spring Security can authorize and protect web requests through http.authorizeRequests(). Spring Security uses standard Filter to establish the interception of web requests, and ultimately achieves authorized access to resources.
Analyze the authorization process:
To intercept requests , authenticated users accessing protected web resources will be intercepted by the subclass of FilterSecurityInterceptor in SecurityFilterChain.
To obtain the resource access policy , FilterSecurityInterceptor will obtain the permission Collection required to access the current resource from DefaultFilterInvocationSecurityMetadataSource, a subclass of SecurityMetadataSource.
SecurityMetadataSource is actually the abstraction of reading the access policy, and the content read is actually the access rule we configured. Reading the access policy is as follows:
http.authorizeRequests() .antMatchers("/r/r1").hasAuthority("p1") .antMatchers("/r/r2").hasAuthority("p2") ...
Finally, FilterSecurityInterceptor will call AccessDecisionManager to make authorization decisions. If the decision passes, access to the resource will be allowed, otherwise access will be prohibited.
3.2.2 Principle analysis of annotation method
Method-based authorization is implemented using Aop.
Process analysis diagram:
4. Session management
4.1 Obtain user identity
After the user is authenticated, the user's information can be saved in the session in order to avoid authentication for every operation of the user. Spring security provides session management. After passing the authentication, the identity information is put into the SecurityContextHolder context. The SecurityContext is bound to the current thread to facilitate obtaining the user identity.
How to write:
@RequestMapping("/getUsername")
public String getUsername(){
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
String username = "";
if(principal instanceof UserDetails){
username = ((UserDetails) principal).getUsername();
}else{
username= principal.toString();
}
return username;
}
4.2 Session control
We can control exactly when a session is created and how Spring Security interacts with it via the following options:
mechanism | describe |
---|---|
always | If no session exists, create one |
ifRequired | Create a Session if needed (default) when logging in |
never | SpringSecurity will not create a Session, but if a Session is created elsewhere in the application, Spring Security will use it. |
stateless | SpringSecurity will never create a Session, nor use a Session |
Configure this option through the following configuration methods:
.and() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
By default, Spring Security will create a new Session for each user who successfully logs in, which is ifRequired .
If you select never , it instructs Spring Security not to create a session for users who successfully log in. However, if your application creates a new session somewhere, Spring Security will use it.
If you use stateless , it means that Spring Security will not create a session for users who log in successfully, and your application will not allow new sessions. And it will imply that cookies are not used, so every request needs to be re-authenticated. This stateless architecture is suitable for REST APIs and their stateless authentication mechanisms.
5. Integrated authentication and authorization in RBAC
5.1 Integrated authentication
####5.1.1 Import dependencies
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
When I started the project, I found that there was a cross-domain problem when accessing the front-end interface.
Reason: Because cross-domain will send a pre-request to see if the server supports cross-domain, but this pre-request will also be intercepted. Before, we judged whether it was a handlerMethod in the interceptor to decide whether to release it, but now we use SpringSecurity. It was intercepted by SpringSecurity.
5.1.2 Configure rules
@Override
protected void configure(HttpSecurity http) throws Exception {
//进制 crsf
http.csrf().disable();
//配置拦截规则
http.authorizeRequests().
antMatchers("/api/code","/api/login","/api/logout").
permitAll().
anyRequest().
authenticated();
}
Restart access: The verification code has been released, but when you click on login to call login, it is still the login method we wrote ourselves before. We want SpringSecurity to help us authenticate and annotate the previous login code in the LoginServiceImpl implementation class.
5.1.3 Customize UserDetailService
@Service
public class UserDetailServiceImpl implements UserDetailsService {
@Autowired
private EmployeeMapper employeeMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名去查询数据
if(StringUtils.isEmpty(username)){
return null;
}
Employee employee = employeeMapper.selectByUsername(username);
return User.withUsername(employee.getUsername()).password(employee.getPassword()).authorities("p1").build();
}
}
Because our data is stored in the database, we need to customize the UserDetailService to query the database data during operation, but a new problem arises. The class we defined will not be called.
Thinking: Why is our UserDetailService not called? It could be called during learning before.
Reason: The form submission method we used before directly used its form processing filter, but now we use ajax submission on the front end instead of form submission. Its expression submission filter cannot handle it and needs to be handled by ourselves.
5.1.4 Join authenticator
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder(){
return NoOpPasswordEncoder.getInstance();
}
5.1.5 Call the authenticator in loginService for authentication
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginVO.getUsername(),loginVO.getPassword());
Authentication authenticate =
authenticationManager.authenticate(token);
User user = (User) authenticate.getPrincipal();
We encountered a new problem here and found that User is returned, but we need to put the Employe object in redis. User only contains the account password and permission information of the currently logged in user.
5.1.6 Custom User
@Getter
@Setter
public class LoginUser implements UserDetails {
private Employee employee;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return employee.getPassword();
}
@Override
public String getUsername() {
return employee.getUsername();
}
/**
* 账户是否未过期,过期无法验证
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 指定用户是否解锁,锁定的用户无法进行身份验证
*
* @return
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
*
* @return
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 是否可用 ,禁用的用户不能身份验证
*
* @return
*/
@Override
public boolean isEnabled() {
return true;
}
}
In UserDetailServiceImpl
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//根据用户名去查询数据
if(StringUtils.isEmpty(username)){
return null;
}
Employee employee = employeeMapper.selectByUsername(username);
LoginUser loginUser = new LoginUser();
loginUser.setEmployee(employee);
return loginUser;
// return User.withUsername(employee.getUsername()).password(employee.getPassword()).authorities("p1").build();
}
loginServiceImpl 中
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
Employee employee = loginUser.getEmployee();
Now I can log in, but I found that the access department manages these resources and the following problems occur. Is there another cross-domain problem? Haven’t we already solved the cross-domain problem?
Reason: Our matching rules except "/api/code", "/api/login", and "/api/logout" all need to be intercepted to determine whether to authenticate. In SpringSecurity, SecurityContextHolder.getContext().getAuthentication() Get the current user information to see if you are logged in.
5.1.8 Custom filter
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private RedisUtils redisUtils;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String userId = request.getHeader("userId");
String objJson = redisUtils.get(Constant.LOGIN_EMPLOYEE + userId);
if(!StringUtils.isEmpty(objJson)){
Employee employee = JSON.parseObject(objJson, Employee.class);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(employee.getUsername(),employee.getPassword());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
filterChain.doFilter(request,response);
}
}
Add configuration:
http.addFilterBefore(authenticationTokenFilter,
UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(corsFilter,
JwtAuthenticationTokenFilter.class);
5.2 Integrated authorization
5.2.1 Query authorization information
public Collection<? extends GrantedAuthority> getAuthorities() {
// 先查询出来当前用户是否是超级管理员
PermissionMapper permissionMapper = SpringUtils.getBean(PermissionMapper.class);
List<GrantedAuthority> list = new ArrayList<>();
if(employee.isAdmin()){
// 如果是分配所有权限
List<Permission> permissions = permissionMapper.selectAll();
// 如果不是分配用户所拥有的权限
for (Permission permission : permissions) {
list.add(new SimpleGrantedAuthority(permission.getExpression()));
}
}else{
//根据用户id 查询用户所拥有权限结合
List<String> expressions = permissionMapper.queryPermissionByEmpId(employee.getId());
for (String expression : expressions) {
list.add(new SimpleGrantedAuthority(expression));
}
}
return list;
}
5.2.2 Add permissions to filter
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String userId = request.getHeader("userId");
if(!StringUtils.isEmpty(userId)){
String objJson = redisUtils.get(Constant.LOGIN_EMPLOYEE + userId);
Employee employee = JSON.parseObject(objJson, Employee.class);
LoginUser loginUser = new LoginUser();
loginUser.setEmployee(employee);
UsernamePasswordAuthenticationToken token =
new UsernamePasswordAuthenticationToken(employee.getUsername(),employee.getPassword(),loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(token);
}
filterChain.doFilter(request,response);
}
5.2.3 Turn on annotation support
Post annotation on startup class: @EnableGlobalMethodSecurity(prePostEnabled = true)
@SpringBootApplication
@MapperScan(basePackages = "cn.wolfcode.mapper")
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class,args);
}
}
Method post annotation: @PreAuthorize("hasAuthority('role:queryByRoleId')")
5.2.4 Solve the problem of failure to load permissions
Reason: Since the principle of our annotation permission interception is to use Aop, the Controller will be enhanced. We annotate that the method cannot be obtained through the proxy class.
solve:
//3 从 Controller 中拿到所有的方法
Method[] methods = controller.getClass().getSuperclass().getDeclaredMethods();
6. JWT
6.1 Introduction to JWT
jsonwebtoken (JWT) is an open standard (rfc7519) that defines a compact, self-contained way to securely transmit information as a JSON object between parties. This information can be verified and trusted because it is digitally signed. jwt can be signed with a secret (using the HMAC algorithm) or with a public/private key pair using RSA or ECDSA
In layman's terms: JWT, short for JSON Web Token, is used as a token in web applications in the form of JSON, and is used to securely transmit information as a JSON object between parties. During the data transmission process, data encryption, signature and other related processing can also be completed.
6.2 What JWT can do
1. Authorization
This is the most common scenario for using JWT. Once the user is logged in, each subsequent request will include the JWT, allowing the user to access the routes, services, and resources allowed by that token. Single sign-on is a feature where JWT is widely used today as it has little overhead and can be easily used across different domains.
2. Information exchange
JSON Web Tokens are a great way to securely transfer information between parties. Because JWTs can be signed (e.g., using a public/private key pair), you can be sure that the sender is who they say they are. Additionally, since the signature is calculated using the header and payload, you can also verify that the content has not been tampered with.
7.3 Why use JWT
Based on traditional Session authentication
defect:
1. After each user is authenticated by our application, our application must make a record on the server to facilitate the identification of the user's next request. Generally speaking, the session is saved in the memory, and as the authenticated user If the number increases, the server-side overhead will increase significantly.
2 Because user identification is based on cookies, if the cookie is intercepted, the user will be vulnerable to cross-site request forgery attacks.
Based on JWT authentication
Advantages of jwt:
Compact: It can be sent through URL, POST parameters or HTTP header, because the data volume is small and the transmission speed is fast.
Self-contained: The payload contains all the information required by the user, avoiding multiple database queries.
Because Token is stored on the client in JSON encrypted form, JWT is cross-language and, in principle, supported by any web form.
6.3 Introduction to JWT structure
6.3.1 Token composition
1.Header
2.Payload
3.Signature
Therefore, a JWT usually looks like this: xxxxx.yyyyy.zzzzz Header.Payload.Signature
7.2.2 Header part
The header usually consists of two parts: the type of token (i.e. JWT) and the signing algorithm used, such as HMAC SHA256 or RSA. It will use Base64 encoding to form the first part of the JWT structure.
Note: Base64 is an encoding, which means it can be translated back to its original form. It is not an encryption process.
{
"alg": "HS256",
"typ": "JWT"
}
6.3.2 Payload part
The second part of the token is the payload, which contains the claims. Claims are statements about entities (usually users) and other data. Likewise, it will use Base64 encoding to form the second part of the JWT structure
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
6.3.3 Signature part
The first two parts are encoded using Base64, that is, the front end can decode and know the information inside. Signature needs to use the encoded header and payload and a key we provide, and then use the signature algorithm (HS256) specified in the header to sign. The purpose of the signature is to ensure that the JWT has not been tampered with
如:
HMACSHA256(base64UrlEncode(header) + “.” + base64UrlEncode(payload),secret);
Signature purpose
The last step of the signing process is actually to sign the header and payload content to prevent the content from being tampered with. If someone decodes the content of the header and payload, modifies it, then encodes it, and finally adds the previous signature combination to form a new JWT, then the server will determine that the signature formed by the new header and payload is attached to the JWT. The signatures are different. If you want to sign new headers and payloads, the resulting signature will be different if you don't know the key used by the server for encryption.
6.4 JWT usage
6.4.1 Introducing dependencies
<!--引入jwt-->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
6.4.2 Generate token
//生成令牌
String token = JWT.create()
.withClaim("username", "张三")//设置自定义用户名
.sign(Algorithm.HMAC256("token!Q2W#E$RW"));//设置签名 保密 复杂
//输出令牌
System.out.println(token);
Generate results :
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhdWQiOlsicGhvbmUiLCIxNDMyMzIzNDEzNCJdLCJleHAiOjE1OTU3Mzk0NDIsInVzZXJuYW1lIjoi5byg5LiJIn0.aHmE3RNqvAjFr_dvyn_sD2VJ46P7EGiS5OBMO_TI5jg
6.4.3 Parsing data based on tokens
JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256("token!Q2W#E$RW")).build();
DecodedJWT decodedJWT = jwtVerifier.verify(token);
System.out.println("用户名: " + decodedJWT.getClaim("username").asString());
6.4.4 Common exceptions
- SignatureVerificationException: 签名不一致异常
- TokenExpiredException: 令牌过期异常
- AlgorithmMismatchException: 算法不匹配异常
- InvalidClaimException: 失效的payload异常
6.4.6 Integrating JWT in RBAC
6.4.6.1 Extraction tool class
package cn.wolfcode.util;
/**
* create By fjl
*/
@Component
@Getter
@Setter
public class JWTUtils {
@Value("${jwt.scret}")
public String scret;
@Value("${jwt.head}")
public String head;
public String createTokenMap(Map<String,String> map) {
JWTCreator.Builder builder = JWT.create();
for (Map.Entry<String, String> entry : map.entrySet()) {
builder.withClaim(entry.getKey(), entry.getValue());
}
String token = builder.sign(Algorithm.HMAC256(scret));
return token;
}
public String createToken(String key , String value) {
JWTCreator.Builder builder = JWT.create();
builder.withClaim(key,value);
String token = builder.sign(Algorithm.HMAC256(scret));
return token;
}
s
public String getToken1(String token,String key){
//先验证签名
JWTVerifier verifier = JWT.require(Algorithm.HMAC256(scret)).build();
//验证其他信息
DecodedJWT verify = verifier.verify(token);
String value = verify.getClaim(key).asString();
return value;
}
}
6.4.6.2 Add configuration
jwt:
scret: abced
head: Authencation
6.4.6.3 Modify LoginServiceImpl
@Override
public String login(LoginVO loginVO) {
//参数校验
if(loginVO==null){
throw new BusinessException("非法操作");
}
if(StringUtils.isEmpty(loginVO.getUsername()) || StringUtils.isEmpty(loginVO.getPassword())){
throw new BusinessException("账号密码不能为空");
}
if(StringUtils.isEmpty(loginVO.getCode())){
throw new BusinessException("验证码不能为空");
}
// 从 redis 中获取密码
String redisCode = redisUtils.get(Constant.VERFI_CODE_PREFIX + loginVO.getUuid());
boolean flag = VerifyCodeUtil.verification(redisCode, loginVO.getCode(), true);
if(!flag){
throw new BusinessException("验证码不正确");
}
// 根据账号密码去查询数据
// Employee employee = employeeService.login(loginVO.getUsername(),loginVO.getPassword());
// if(employee == null){
// throw new BusinessException("账号密码错误");
// }
UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginVO.getUsername(),loginVO.getPassword());
Authentication authenticate =
authenticationManager.authenticate(token);
LoginUser loginUser = (LoginUser) authenticate.getPrincipal();
Employee employee = loginUser.getEmployee();
//创建 token login_user:uuid
String uuid = UUID.randomUUID().toString();
String jwtToken = jwtUtils.createToken1(Constant.JWT_TOKEN_KEY, uuid);
// 把当前登录用户放到 redis 中为了后去判断是否登录做铺垫
// login_employee:id employee
redisUtils.set(Constant.LOGIN_EMPLOYEE+uuid, JSON.toJSONString(employee),Constant.EXPRE_TIME);
// 把当前登录用户所拥有的权限放到 session 中
// 根据当前用户查询 用户拥有权限表达式
List<String> expressions = permissionService.queryPermissionByEmpId(employee.getId());
redisUtils.set(Constant.EMPLOYEE_EXPRESSIONS+uuid,JSON.toJSONString(expressions),Constant.EXPRE_TIME);
return jwtToken;
}
6.4.6.4 Modify JwtAuthenticationTokenFilter
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader(jwtUtils.getHead());
if (!StringUtils.isEmpty(token)) {
String uuid = jwtUtils.getToken1(token, Constant.JWT_TOKEN_KEY);
String objJson = redisUtils.get(Constant.LOGIN_EMPLOYEE + uuid);
if(!StringUtils.isEmpty(objJson)){
Employee employee = JSON.parseObject(objJson, Employee.class);
LoginUser loginUser = new LoginUser();
loginUser.setEmployee(employee);
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(employee.getUsername(), employee.getPassword(), loginUser.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
filterChain.doFilter(request, response);
}
6.4.6.5 Modify front-end main.js
// 请求拦截
axios.interceptors.request.use(function(request){
const token = window.sessionStorage.getItem("token");
if(token){
request.headers.Authencation=token;
}
return request;
},function(err){
return Promise.reject(err)
})
6.4.6.6 Modify front-end login.js
async login() {
const {
data: res } = await this.$http.post("login", this.loginForm);
console.log(res);
if (res.code != 200) {
console.log("登录失败");
} else {
console.log("登录成功");
window.sessionStorage.setItem("token", res.data);
this.$router.push("/main");
}
},
}
6.5.6.7 Modify routing index.js
router.beforeEach((to,from,next) =>{
console.log("router---beforeEach")
// to 将要访问的路径
// from 代表从哪个路径跳转而来
// next 是一个函数,表示放行
// next() 放行 next('/login') 强制跳转
if(to.path==="/login") return next();
const token=window.sessionStorage.getItem("token");
console.log(token)
if(token) return next();
next("/login")
});
7. Appendix: HttpSecurity configuration items
method | illustrate |
---|---|
openidLogin() | For OpenId based authentication |
headers() | Add security headers to response |
cors() | Configure Cross-Origin Resource Sharing (CORS) |
sessionManagement() | Allow configuration of session management |
portMapper() | Redirect to HTTPS or from HTTPS to HTTP. By default, Spring Security uses a PortMapperImpl to map HTTP port 8080 to HTTPS port 8443, and HTTP port 80 to HTTPS port 443. |
jee() | Configure container-based pre-authentication. In this case, authentication is managed by the Servlet container |
x509() | Configure x509-based authentication |
rememberMe | Allows configuration of "remember me" verification |
authorizeRequests() | Allow restricted access based on use of HttpServletRequest |
requestCache() | Allow configuration request caching |
exceptionHandling() | Allow configuration error handling |
securityContext() | Set the management of SecurityContext on the SecurityContextHolder between HttpServletRequests. When using WebSecurityConfifigurerAdapter this will |
servletApi() | Integrate the HttpServletRequest method into the SecurityContext with the values found on it. This will be applied automatically when using WebSecurityConfifigurerAdapter |
csrf() | Add CSRF support, enabled by default when using WebSecurityConfifigurerAdapter |
logout() | Added logout support. This will be applied automatically when using WebSecurityConfifigurerAdapter. By default, accessing the URL "/logout" invalidates the HTTP Session. |
anonymous() | Allows configuration of anonymous user representation methods. This is automatically applied when used in conjunction with WebSecurityConfifigurerAdapter. By default, anonymous users will use |
formLogin() | Specifies support for forms-based authentication. If FormLoginConfifigurer#loginPage(String) is not specified, a default login page will be generated |
oauth2Login() | Configure authentication against an external OAuth 2.0 or OpenID Connect 1.0 provider |
requiresChannel() | Configure channel security. For this configuration to be useful, at least one mapping to the required channel must be provided |
httpBasic() | Configure Http Basic Authentication |
addFilterAt() | Allow configuration error handling |
exceptionHandling() | Add a filter at the specified Filter class location |