Spring Security: 2 [Principle Analysis, Session Management, Integrated Authentication and Authorization in RBAC, JWT]

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:

Insert image description here

The implementation of Spring Security functions is mainly completed by a series of filter chains cooperating with each other.

Insert image description here

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

Insert image description here

Let’s analyze the certification process in detail:

  1. 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.

  2. The filter then submits the Authentication to the authentication manager (AuthenticationManager) for authentication

  3. 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).

  4. 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:

Insert image description here

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?

Insert image description here

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.

Insert image description here

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.

Insert image description here

3.2.2 Principle analysis of annotation method

Method-based authorization is implemented using Aop.

Process analysis diagram:

Insert image description here

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?

Insert image description here

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

Insert image description here

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

Insert image description here

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.

Insert image description here

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

Guess you like

Origin blog.csdn.net/m0_52896752/article/details/132904915