前言:
在上一篇文章中,介紹了springSecurity框架搭建后,這個項目啟動后,默認是必須登錄后,才能進入其他頁面的。而這個登錄頁面是springSecurity自帶的登錄界面。在實際的開發(fā)過程中,我們要寫一個漂亮的登錄界面,這就需要自定義登錄頁面了,那么接下來,就介紹自定義登錄頁面如何實現(xiàn)。
本篇內(nèi)容包括:自定義登錄頁面,自定義成功攔截器,自定義失敗攔截器,自定義登錄表單名稱,action。
一、步驟
- 創(chuàng)建登錄界面
login.html。 - 創(chuàng)建路徑映射對應(yīng)的
Controller。 - 創(chuàng)建
WebSecurityConfigurerAdapter的子類WebSecurityConfigurer,用@Configuration注解該子類。-
@Configuration中所有使用@Bean注解的方法都會被動態(tài)代理,一次調(diào)用該方法返回的都是同一個實例。 - 并重寫里面的
configure()方法。 - 并創(chuàng)建
passwordEncoder()方法。在方法上使用@Bean注解。
-
- 創(chuàng)建
UserDetailsService的實現(xiàn)類MyUserDetails類中,用@Component注解。- 使用
@Autowired,注入PasswordEncoder對象 。 - 重寫認證根據(jù)用戶名認證用戶的方法
loadUserByUsername()。 - 設(shè)置用戶的密碼,角色。
- 使用
- 在
WebSecurityConfigurerAdapter的子類中WebSecurityConfigurer,注入UserDetailsService的實現(xiàn)類,并在config()方法中配置。 - 分別創(chuàng)建自定義登錄成功,失敗處理器,對應(yīng)實現(xiàn)
AuthenticationSuccessHandler,AuthenticationFailureHandler接口。使用@Component將它們放入容器中。以備WebSecurityConfigurerAdapter的子類使用。 - 在
WebSecurityConfigurerAdapter的子類中WebSecurityConfigurer,注入AuthenticationSuccessHandler,AuthenticationFailureHandler接口的處理器實現(xiàn)類,并在config進行配置。 -
application.properties中配置視圖映射 。
注意:springboot使用的是使用
thymeleaf的html5模板。
查看項目結(jié)構(gòu)示意圖。

二、清單說明
清單一、springboot是使用thymeleaf的html5模板。
步驟1.1中創(chuàng)建
login.html頁面,頭部<html>中,標簽中引入<html lang="en" xmlns:th="http://www.thymeleaf.org">
清單二、springboot約定的資源文件映射路徑
springboot約定的資源文件映射路徑:
gradle項目資源目錄:src/main/java/resources
spring-boot項目靜態(tài)文件目錄:/src/main/java/resources/static
spring-boot項目模板文件目錄:/src/main/java/resources/templates
步驟1.8中配置
application.properties中配置模板視圖映射。springboot因為已經(jīng)默認約定好了資源文件映射路徑。其實在這里可以不用配置。我還是要把它,貼在這里,一邊更好的來理解。
#視圖 這是默認的配置
spring.thymeleaf.cache=false
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
#檢查模板是否存在,然后再呈現(xiàn)
spring.thymeleaf.check-template-location=true
清單三 、spring-security登錄界面的name。
在spring-security中默認設(shè)置的用戶登錄表單中的name名稱為username和password 。
默認設(shè)置的登錄表單中name名稱,用戶名密碼。同時還默認設(shè)置了登錄頁面表單請求的
action=/login,以及必須為post方式請求。可以在spring-security源碼中查看:UsernamePasswordAuthenticationFilter。
【擴展閱讀】節(jié)選源碼片段一:UsernamePasswordAuthenticationFilter 定義的登錄名單中name名稱
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username";
public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password";
private String usernameParameter = "username";
private String passwordParameter = "password";
private boolean postOnly = true;
public UsernamePasswordAuthenticationFilter() {
super(new AntPathRequestMatcher("/login", "POST"));
}
// ... ...
}
清單四 、login.html。
<div align="center">
<h2>自定義登錄頁面</h2>
<form th:action="@{/login}" method="post">
<label>
<span>用戶名:</span>
<input type="text" name="username">
<!--<input type="text" name="uname">-->
</label>
<br/>
<label>
<span>密碼:</span>
<input type="password" name="password">
<!--<input type="password" name="psd">-->
</label>
<input type="submit" class="submit input_button" value="登錄">
</form>
</div>
注意:
【要點1】form 表單提交的action 是login,及和登錄頁面同名(和路徑映射的controller同名)。
- 如果你改成其他的action,比如說為:
/login/form,則需要在WebSecurityConfigurerAdapter的子類
的 config()方法中添加.loginProcessingUrl("/login/form") //form 表單action
2.如果form表單的action如果和登錄頁面同名(和路徑映射的controller同名)【不一定非得是login】,那么無需添加.loginProcessingUrl("")這一句,它會默認請求。
3.以上1,2兩種方式,action的值是否和請求頁面路徑的controller映射一致,都不需要去寫action對應(yīng)路徑的controller路徑。
【要點2】form表單中用戶名密碼,自定義
1.需要在WebSecurityConfigurerAdapter的子類
的 config()方法中添加.usernameParameter("uname")和.passwordParameter("psd")。
清單五、路徑映射的controller
步驟1.2 創(chuàng)建路徑映射對應(yīng)的Controller 。
@Controller
public class LoginController {
@GetMapping("/login")
public String login() {
return "login";
}
}
清單六、創(chuàng)建失敗,成功處理器
步驟:1.6 分別創(chuàng)建自定義登錄成功,失敗處理器,對應(yīng)實現(xiàn)AuthenticationSuccessHandler,AuthenticationFailureHandler接口。使用@Component將它們放入容器中。以備WebSecurityConfigurerAdapter 的子類使用。
代碼1:失敗處理器
@Component
public class FailureAuthenticationHandler implements AuthenticationFailureHandler {
protected Logger logger=LoggerFactory.getLogger(getClass());
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
logger.info("登錄失敗");
httpServletResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(e));
// httpServletResponse.getWriter().write(objectMapper.writeValueAsString(new SimpleResonse(e.getMessage())));
}
}
代碼2:成功處理器
@Component
public class SuccessAuthenticationHandler implements AuthenticationSuccessHandler {
protected Logger logger=LoggerFactory.getLogger(getClass());
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
logger.info("登錄成功");
httpServletResponse.setContentType("application/json;charset=UTF-8");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(authentication)+"/n"+"登錄成功了");
}
}
清單七、創(chuàng)建用戶信息:定義登錄密碼
步驟:1.4 創(chuàng)建
UserDetailsService的實現(xiàn)類MyUserDetails。
@Component
public class MyUserDetails implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
String password=passwordEncoder.encode("123456");
return new User(username,password,AuthorityUtils.commaSeparatedStringToAuthorityList("admin"));
}
}
清單八、創(chuàng)建WebSecurityConfigurerAdapter的子類 WebSecurityConfigurer
@Configuration
public class WebSecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
private FailureAuthenticationHandler failureHandler;
@Autowired
private SuccessAuthenticationHandler successHandler;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
// 自定義login.html登錄界面
http.formLogin().loginPage("/login")
// .loginProcessingUrl("/login) //form 表單action
.failureHandler(failureHandler)
.successHandler(successHandler)
//.usernameParameter("uname")
//.passwordParameter("psd")
.and()
.authorizeRequests()
.antMatchers("/login").permitAll()
// 除了login.html頁面以外都需要身份認證 (一定要記得添加這一句,否則就是死循環(huán))
.anyRequest()
.authenticated()
;
}
}
注意:
【要點1】:一定要把自定義登錄界面給放開權(quán)限。
即.antMatchers("/login").permitAll(),否則就是死循環(huán)。
最后運行項目 ,
登錄:用戶名,隨便輸入,密碼為:123456
登錄成功后,會顯示
{"authorities":[{"authority":"admin"}],"details":{"remoteAddress":"127.0.0.1","sessionId":"7BD95EAEB7B20BDAA5CCB18C0C601684"},"authenticated":true,"principal":{"password":null,"username":"2222","authorities":[{"authority":"admin"}],"accountNonExpired":true,"accountNonLocked":true,"credentialsNonExpired":true,"enabled":true},"credentials":null,"name":"2222"}/n登錄成功了
登錄失敗了,會顯示
{"cause":null,"stackTrace":[{"methodName":"additionalAuthenticationChecks","fileName":"DaoAuthenticationProvider.java","lineNumber":89,"className":"org.springframework.security.authentication.dao.DaoAuthenticationProvider","nativeMethod":false},{"methodName":"authenticate","fileName":"AbstractUserDetailsAuthenticationProvider.java","lineNumber":166,"className":"org.springframework.security.authentication.dao.AbstractUserDetailsAuthentication
Provider","nativeMethod":false},
// ... ... 省略好多內(nèi)容
{"methodName":"run","fileName":"Thread.java","lineNumber":748,"className":"java.lang.Thread","nativeMethod":false}],"localizedMessage":"壞的憑證","message":"壞的憑證","suppressed":[]}