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();
}
至此,預設登入頁及配置,已經可以清楚了。