1. 程式人生 > >spring security 5.x 使用及分析(二:自定義配置—初階)

spring security 5.x 使用及分析(二:自定義配置—初階)

二、自定義配置(初階):

自定義的配置,就要修改一些預設配置的資訊,從那開始入手呢?

1、第一步:建立Spring Security 的Java配置,改配置建立一個名為springSecurityFilterChain的servlet過濾器,它負責應用程式的安全(保護程式URL,驗證提交的使用者名稱密碼,重定向到登入表單等)······

按照第一步,建立一個配置類並實現WebMvcConfigurer介面(User.withDefaultPasswordEncoder()方法以經不推薦使用了,但是文件還是用了這個方法):

@EnableWebSecurity
public class WebSecurityConfig implements WebMvcConfigurer {

    @Bean
    public UserDetailsService userDetailsService() throws Exception {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withDefaultPasswordEncoder().username("user").password("password").roles("USER").build());
        return manager;
    }
}

然後我們在重新啟動一下工程,在日誌中已經沒有列印密碼的資訊了,輸入配置中設定的使用者名稱和密碼,登入成功了。

2、第二步:註冊springSecurityFilterChain,這可以用Servlet 3.0 以後版本的Spring's WebApplicationInitializer support在Java 配置中實現。Spring Security 提供了一個基礎類 AbstractSecurityWebApplicationInitializer,這個類能夠幫你實現註冊springSecurityFilterChain,使用這個基礎類的方式取決於專案中是否使用Spring,這裡按照使用了Spring的方式配置。

public class MvcWebApplicationInitializer extends
		AbstractAnnotationConfigDispatcherServletInitializer {

	@Override
	protected Class<?>[] getRootConfigClasses() {
        //這裡的WebSecurityConfig.class 就是前面定義的配置類
		return new Class[] { WebSecurityConfig.class };
	}

	// ... other overrides ...
}

這樣就實現了為工程中的每個URL實現通過springSecurityFilterChain攔截 ,那登入頁面事怎麼出來的呢?

繼續看文件,在WebSecurityConfigurerAdapter 類中,有一個方法 configure(HttpSecurity http):

     /**
	 * Override this method to configure the {@link HttpSecurity}. Typically subclasses
	 * should not invoke this method by calling super as it may override their
	 * configuration. The default configuration is:
	 *
	 * <pre>
	 * http.authorizeRequests().anyRequest().authenticated().and().formLogin().and().httpBasic();
	 * </pre>
	 *
	 * @param http the {@link HttpSecurity} to modify
	 * @throws Exception if an error occurs
	 */
	// @formatter:off
	protected void configure(HttpSecurity http) throws Exception {
		logger.debug("Using default configure(HttpSecurity). If subclassed this will potentially override subclass configure(HttpSecurity).");

		http
			.authorizeRequests()
				.anyRequest().authenticated()
				.and()
			.formLogin().and()
			.httpBasic();
	}

這個方法攔截所有路徑並跳轉到基礎登入頁,就是預設的登入頁。點選formLogin()方法,進入到HttpSecurity類中,會看到:

提示看FormLoginConfigurer中的loginPage()方法,但是這裡並沒有什麼特別有用的東西,可以看到的是,這個方法返回一個FormLoginConfigurer型別的資料,進入FormLoginConfigurer類中,在這個類的描述有這樣一段話:

可以看出,一個預設的login頁在這裡被建立,在這個類最後有一個方法:

這個方法,初始化一個預設登入頁的過濾器,可以看到第一句程式碼,預設的過濾器是DefaultLoginPageGeneratingFilter,下面是設定一些必要的引數,進入到這個過濾器中:

在描述中可以看到,如果沒有配置login頁,這個過濾器會被建立,然後看doFilter()方法:

登入頁面的配置是通過generateLoginPageHtml()方法建立的,再來看看這個方法內容:

private String generateLoginPageHtml(HttpServletRequest request, boolean loginError,
			boolean logoutSuccess) {
		String errorMsg = "none";

		if (loginError) {
			HttpSession session = request.getSession(false);

			if (session != null) {
				AuthenticationException ex = (AuthenticationException) session
						.getAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
				errorMsg = ex != null ? ex.getMessage() : "none";
			}
		}

		StringBuilder sb = new StringBuilder();

		sb.append("<html><head><title>Login Page</title></head>");

		if (formLoginEnabled) {
			sb.append("<body onload='document.f.").append(usernameParameter)
					.append(".focus();'>\n");
		}

		if (loginError) {
			sb.append("<p style='color:red;'>Your login attempt was not successful, try again.<br/><br/>Reason: ");
			sb.append(errorMsg);
			sb.append("</p>");
		}

		if (logoutSuccess) {
			sb.append("<p style='color:green;'>You have been logged out</p>");
		}

		if (formLoginEnabled) {
			sb.append("<h3>Login with Username and Password</h3>");
			sb.append("<form name='f' action='").append(request.getContextPath())
					.append(authenticationUrl).append("' method='POST'>\n");
			sb.append("<table>\n");
			sb.append("	<tr><td>User:</td><td><input type='text' name='");
			sb.append(usernameParameter).append("' value='").append("'></td></tr>\n");
			sb.append("	<tr><td>Password:</td><td><input type='password' name='")
					.append(passwordParameter).append("'/></td></tr>\n");

			if (rememberMeParameter != null) {
				sb.append("	<tr><td><input type='checkbox' name='")
						.append(rememberMeParameter)
						.append("'/></td><td>Remember me on this computer.</td></tr>\n");
			}

			sb.append("	<tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n");
			renderHiddenInputs(sb, request);
			sb.append("</table>\n");
			sb.append("</form>");
		}

		if (openIdEnabled) {
			sb.append("<h3>Login with OpenID Identity</h3>");
			sb.append("<form name='oidf' action='").append(request.getContextPath())
					.append(openIDauthenticationUrl).append("' method='POST'>\n");
			sb.append("<table>\n");
			sb.append("	<tr><td>Identity:</td><td><input type='text' size='30' name='");
			sb.append(openIDusernameParameter).append("'/></td></tr>\n");

			if (openIDrememberMeParameter != null) {
				sb.append("	<tr><td><input type='checkbox' name='")
						.append(openIDrememberMeParameter)
						.append("'></td><td>Remember me on this computer.</td></tr>\n");
			}

			sb.append("	<tr><td colspan='2'><input name=\"submit\" type=\"submit\" value=\"Login\"/></td></tr>\n");
			sb.append("</table>\n");
			renderHiddenInputs(sb, request);
			sb.append("</form>");
		}

		if (oauth2LoginEnabled) {
			sb.append("<h3>Login with OAuth 2.0</h3>");
			sb.append("<table>\n");
			for (Map.Entry<String, String> clientAuthenticationUrlToClientName : oauth2AuthenticationUrlToClientName.entrySet()) {
				sb.append(" <tr><td>");
				sb.append("<a href=\"").append(request.getContextPath()).append(clientAuthenticationUrlToClientName.getKey()).append("\">");
				sb.append(HtmlUtils.htmlEscape(clientAuthenticationUrlToClientName.getValue(), "UTF-8"));
				sb.append("</a>");
				sb.append("</td></tr>\n");
			}
			sb.append("</table>\n");
		}

		sb.append("</body></html>");

		return sb.toString();
	}

至此,預設登入頁及配置,已經可以清楚了。