Spring Security Study Notes (4) Logout and login, get user data

Reference Video ( Bad Programming People )

logout

By default, if we are already logged in, we /logoutwill be logged out when accessing the interface in get mode, and we will be prompted to log in again when we access restricted resources next time.
We can add the following configuration in the configuration of the ss filter

.and()
                .logout()
                .logoutUrl("/logout")//指定注销登录的接口,默认就是get方式
                .invalidateHttpSession(true)//是否让当前的session失效
                .clearAuthentication(true)//清除认证标记
                .logoutSuccessUrl("")//可以指定退出登录之后跳转的路径

The default logout method is get, and we can configure other logout methods.

.and()
                .logout()
//                .logoutUrl("/logout")//指定注销登录的接口
                .logoutRequestMatcher(new OrRequestMatcher(
                        new AntPathRequestMatcher("/aa","GET"),
                        new AntPathRequestMatcher("/bb","POST")
                ))
                .invalidateHttpSession(true)//是否让当前的session失效
                .clearAuthentication(true)//清除认证标记
                .logoutSuccessUrl("")//可以指定退出登录之后跳转的路径

The front-end and back-end separation project logout successfully returns a json message

This is the same as the implementation of the previous front-end and back-end separation projects.
Inherited class LogoutSuccessHandleroverride method

@Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
    
    
        HashMap<String,Object> hashMap = new HashMap<>();
        hashMap.put("code",200);
        hashMap.put("msg","注销登录成功。");
        response.setContentType("application/json;charset=utf-8");
        String s = new ObjectMapper().writeValueAsString(hashMap);
        response.getWriter().write(s);
    }

configuration inside

.and()
                .logout()
//                .logoutUrl("/logout")//指定注销登录的接口
                .logoutRequestMatcher(new OrRequestMatcher(
                        //指定注销登录的接口和方式
                        new AntPathRequestMatcher("/aa","GET"),
                        new AntPathRequestMatcher("/bb","POST")
                ))
                .invalidateHttpSession(true)//是否让当前的session失效
                .clearAuthentication(true)//清除认证标记
//                .logoutSuccessUrl("")//可以指定退出登录之后跳转的路径
                .logoutSuccessHandler(new MyAuthenticationHandler())

get user data

For authenticated users, if we need to use user data when processing business logic, how can we obtain user data at this time?

For projects where the front and back ends are separated, it only needs to be obtained from the back-end code. But for traditional web projects where the front and back ends are together, it is a little more troublesome.
The server will store the user's authentication information in the session. The default session lifetime is 30 minutes. If the user initiates a request with the sessionId, the session lifetime will be refreshed. Every time the user initiates a request, the user's identity information will be taken from the session and placed in theSecurityContextHoldermiddle,SecurityContextHolderData storage in is implemented using ThreadLocal. It is convenient to use in the subsequent process of the request, and at the end of the request, put the data back into the session and thenSecurityContextHolderThe data in is cleared. actuallySecurityContextHolderstored inSecurityContextSecurityContextstored inAuthentication

insert image description here
This involves the use of the strategy pattern.

public class SecurityContextHolder {
    
    
	// ~ Static fields/initializers
	// =====================================================================================

	public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
	public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
	public static final String MODE_GLOBAL = "MODE_GLOBAL";
	public static final String SYSTEM_PROPERTY = "spring.security.strategy";
	private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
	private static SecurityContextHolderStrategy strategy;
	private static int initializeCount = 0;

	static {
    
    
		initialize();
	}

	// ~ Methods
	// ========================================================================================================

	/**
	 * Explicitly clears the context value from the current thread.
	 */
	public static void clearContext() {
    
    
		strategy.clearContext();
	}

	/**
	 * Obtain the current <code>SecurityContext</code>.
	 *
	 * @return the security context (never <code>null</code>)
	 */
	public static SecurityContext getContext() {
    
    
		return strategy.getContext();
	}

	/**
	 * Primarily for troubleshooting purposes, this method shows how many times the class
	 * has re-initialized its <code>SecurityContextHolderStrategy</code>.
	 *
	 * @return the count (should be one unless you've called
	 * {@link #setStrategyName(String)} to switch to an alternate strategy.
	 */
	public static int getInitializeCount() {
    
    
		return initializeCount;
	}

	private static void initialize() {
    
    
		if (!StringUtils.hasText(strategyName)) {
    
    
			// Set default
			strategyName = MODE_THREADLOCAL;
		}

		if (strategyName.equals(MODE_THREADLOCAL)) {
    
    
			strategy = new ThreadLocalSecurityContextHolderStrategy();
		}
		else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
    
    
			strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
		}
		else if (strategyName.equals(MODE_GLOBAL)) {
    
    
			strategy = new GlobalSecurityContextHolderStrategy();
		}
		else {
    
    
			// Try to load a custom strategy
			try {
    
    
				Class<?> clazz = Class.forName(strategyName);
				Constructor<?> customStrategy = clazz.getConstructor();
				strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
			}
			catch (Exception ex) {
    
    
				ReflectionUtils.handleReflectionException(ex);
			}
		}

		initializeCount++;
	}

	/**
	 * Associates a new <code>SecurityContext</code> with the current thread of execution.
	 *
	 * @param context the new <code>SecurityContext</code> (may not be <code>null</code>)
	 */
	public static void setContext(SecurityContext context) {
    
    
		strategy.setContext(context);
	}

	/**
	 * Changes the preferred strategy. Do <em>NOT</em> call this method more than once for
	 * a given JVM, as it will re-initialize the strategy and adversely affect any
	 * existing threads using the old strategy.
	 *
	 * @param strategyName the fully qualified class name of the strategy that should be
	 * used.
	 */
	public static void setStrategyName(String strategyName) {
    
    
		SecurityContextHolder.strategyName = strategyName;
		initialize();
	}

	/**
	 * Allows retrieval of the context strategy. See SEC-1188.
	 *
	 * @return the configured strategy for storing the security context.
	 */
	public static SecurityContextHolderStrategy getContextHolderStrategy() {
    
    
		return strategy;
	}

	/**
	 * Delegates the creation of a new, empty context to the configured strategy.
	 */
	public static SecurityContext createEmptyContext() {
    
    
		return strategy.createEmptyContext();
	}

	@Override
	public String toString() {
    
    
		return "SecurityContextHolder[strategy='" + strategyName + "'; initializeCount="
				+ initializeCount + "]";
	}
}

From the source code above, you can see several storage strategies

  • MODE_THREADLOCAL: This is suitable for situations where there is no multi-threading, because if the user thread opens a sub-thread, the user's identity information cannot be obtained in the sub-thread.
  • MODE_INHERITABLETHREADLOCAL: This mode is suitable for multi-threaded situations. Internally, InheritableThreadLocalthe class is used to realize that the user's identity data can also be obtained in sub-threads.
  • MODE_GLOBAL: This mode is to put the user's data into a global static variable.

Get the user's identity information

@RequestMapping("/private/auth")
    public String getAuth(){
    
    
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        
        return authentication.toString();
    }

custom storage policy

The default mode is
the test case implemented by ThrrealLocal

@RequestMapping("/private/auth")
    public String getAuth(){
    
    
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        System.out.println("父线程中获取");
        System.out.println(authentication.getPrincipal());//获取身份信息
        System.out.println(authentication.getAuthorities());//获取权限信息
        
       new Thread(new Runnable(){
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    Thread.sleep(200);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println("子线程中获取");
                Authentication authentication1 = SecurityContextHolder.getContext().getAuthentication();
                System.out.println(authentication1.getPrincipal());//获取身份信息
                System.out.println(authentication1.getAuthorities());//获取权限信息
            }
        }).start();
        return authentication.toString();
    }

After logging in to access this interface,
insert image description here
insert image description here
you can see that the sub-thread reports a null pointer, which means that the sub-thread gets authenticationnull.

Through the source code above, SecurityContextHolderwe found that
insert image description here
the strategy can be specified directly in the parameters.
Fill in the startup parameters -Dspring.security.strategy=MODE_INHERITABLETHREADLOCAL
insert image description here
and then log in to the access interface.
insert image description here
insert image description here
You can see that the printed sub-thread here can still obtain user identity information.

Of course, we can also set it in the startup class

@SpringBootApplication
public class SpringBootTestApplication {
    
    

    public static void main(String[] args) {
    
    
        SpringApplication.run(SpringBootTestApplication.class, args);
        SecurityContextHolder.setStrategyName("MODE_INHERITABLETHREADLOCAL");
    }

}

In this way, the above effects can also be achieved.

The storage strategy is implemented internally through different interface implementations SecurityContextHolderStrategy. There are only four methods in it.

/*
 * Copyright 2004, 2005, 2006 Acegi Technology Pty Limited
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.security.core.context;

/**
 * A strategy for storing security context information against a thread.
 *
 * <p>
 * The preferred strategy is loaded by {@link SecurityContextHolder}.
 *
 * @author Ben Alex
 */
public interface SecurityContextHolderStrategy {
    
    
	// ~ Methods
	// ========================================================================================================

	/**
	 * Clears the current context.
	 * 清除当前上下文
	 */
	void clearContext();

	/**
	 * Obtains the current context.
	 * 获取当前线程上下文
	 * @return a context (never <code>null</code> - create a default implementation if
	 * necessary)
	 */
	SecurityContext getContext();

	/**
	 * Sets the current context.
	 * 设置线程上下文
	 * @param context to the new argument (should never be <code>null</code>, although
	 * implementations must check if <code>null</code> has been passed and throw an
	 * <code>IllegalArgumentException</code> in such cases)
	 */
	void setContext(SecurityContext context);

	/**
	 * Creates a new, empty context implementation, for use by
	 * <tt>SecurityContextRepository</tt> implementations, when creating a new context for
	 * the first time.
	 * 创建线程上下文
	 * @return the empty context.
	 */
	SecurityContext createEmptyContext();
}

Obtain user identity information in traditional web projects

add dependency

<dependency>
	  <groupId>org.thymeleaf.extras</groupId>
	  <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>

Then introduce the security namespace

 xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5"

define interface

@RequestMapping("/userDetail.html")
    public String userDetail(){
    
    
        System.out.println("userDetail");
        return "userDetail";
    }

write html pagesuserDetail.html

<!DOCTYPE html>
<html lang="zh"
      xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity5">
<head>
    <meta charset="UTF-8">
    <title>权限信息</title>
</head>
<body>
<h2>获取用户身份信息</h2>
<ol>
    <!-- 属性获取,authentication:n. 证明;鉴定;证实 -->
    <li>登录账号:<span sec:authentication="name"></span></li>
    <!-- principal相当于UserDetails信息 -->
    <li>登录账号:<span sec:authentication="principal.username"></span></li>
    <li>登录密码:<span sec:authentication="principal.password"></span></li>
    <li>账号过期:<span sec:authentication="principal.accountNonExpired"></span></li>
    <li>账号锁定:<span sec:authentication="principal.accountNonLocked"></span></li>
    <li>凭证过期:<span sec:authentication="principal.credentialsNonExpired"></span></li>
    <li>账号启用:<span sec:authentication="principal.enabled"></span></li>
    <li>凭证:<span sec:authentication="credentials"></span></li>
    <li>权限和角色:<span sec:authentication="authorities"></span></li>
    <!-- WebAuthenticationDetails实例 -->
    <li>客户端地址:<span sec:authentication="details.remoteAddress"></span></li>
    <li>sessionId:<span sec:authentication="details.sessionId"></span></li>
</ol>
</body>
</html>

Login to access after startup
insert image description here

Guess you like

Origin blog.csdn.net/qq_45401910/article/details/127150024