圖1 思惟導圖css
當咱們在項目中引入 Spring Security 的相關依賴後,默認的就是表單登陸形式;俗話說:「聽人勸,吃飽飯」,既然 Spring Security 已經給咱們安排的明明白白了,咱們就從表單登陸開始吧。html
在開始以前,咱們能夠站在 Spring Security 的角度上思考:若是我本身來實現表單登陸的功能,那麼我須要作哪些工做呢?前端
就我我的而言,我可能會考慮如下幾點:spring
能夠簡單的製做成以下流程圖:數據庫
圖1-1 表單登陸簡單流程圖json
上方屬於咱們本身設想的實現方案,屬於"低配版"模式,下面咱們來看看 Spring Security 是怎麼作的。Spring Security的思路和咱們大同小異,優勢在於其提供了很好的封裝,提升了框架自己的可擴展性。安全
Spring Security 的實現步驟以下:app
製做成流程圖如示:框架
圖1-2 Spring Security表單登陸認證流程圖ide
這時你可能會一臉懵逼:這咋和剛剛咱們本身設想的徹底不同呀~ 又是Manager又是Provider的;莫慌,且聽我慢慢道來。
上面出現了不少新的概念,咱們目前不須要十分細緻的瞭解它們是怎麼發揮做用的,只須要大概知道它們有什麼用的便可;具體的介紹會在下篇《認證(二):表單登陸認證流程源碼解析》娓娓道來。
通過上述的原理探討,咱們大致上能弄懂了整個表單登陸有哪幾個模塊須要處理;可簡單的總結爲3個模塊:
俗話說:「光說不練假把式」,那麼就讓咱們來實戰一番吧。
做爲一個Java Web項目,第一步固然是引入相關依賴;直接引入Spring Boot封裝好的starter便可。
Spring Security 提供了UserDetails接口,用於獲取用戶的基本信息(帳號密碼、權限集合、是否鎖定等等),咱們只須要根據自身的業務場景,實現該接口便可。
Spring Security提供的UserDetails.class接口
自定義業務相關的用戶信息類,業務定義的UserInfo.class必須帶有username和password相關的信息,用於作用戶驗證;項目根據自身需求來判斷是否須要使用下面的幾個boolean方法,若是無相關需求則直接返回true便可。
@Setter
public class UserInfo implements UserDetails {
private String username;
private String password;
/**
* UserDetails的接口
* 用戶權限集,默認須要添加ROLE_做爲前綴
*/
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>(1);
simpleGrantedAuthorities.add(new SimpleGrantedAuthority("ROLE_USER"));
return simpleGrantedAuthorities;
}
/**
* 獲取用戶密碼
*/
@Override
public String getPassword() {
return this.password;
}
/**
* 獲取用戶名
*/
@Override
public String getUsername() {
return this.username;
}
/**
* 帳戶是否未過時 --true則爲未過時
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 帳戶是否未被鎖定
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 帳戶憑證是否未過時
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 帳戶是否可用
*/
@Override
public boolean isEnabled() {
return true;
}
}
在定義完用戶實體UserInfo後,咱們同時也須要提供對應的Service層的API方法,用以進行一些基本的操做,諸如:新增用戶、刪除用戶等。
Spring Security 也提供了對應的Service層接口,UserDetailsService,接口只有一個方法:UserDetails loadUserByUsername(String username);根據用戶名加載用戶信息.
UserDetailsService.class
public interface UserDetailsService {
UserDetails loadUserByUsername(String username) throws UsernameNotFoundException;
}
所以咱們能夠自定義業務相關的UserInfoServiceImpl類,實現Spring Security提供的 UserDetailsService接口
UserInfoServiceImpl.class
/**
* 用戶信息service模塊
*
* UserDetailsService接口爲SpringSecurity內置接口,內部有方法:
* UserDetails loadUserByUsername(String username):如名所得 根據用戶名加載用戶
* 該方法主要是在:DaoAuthenticationProvider中被調用,獲取用戶的信息
*
* @author 小奇
*/
@Slf4j
@Service
public class UserInfoServiceImpl implements UserDetailsService, UserInfoService {
private final UserInfoDAO userInfoDAO;
@Autowired
public UserInfoServiceImpl(UserInfoDAO userInfoDAO) {
this.userInfoDAO = userInfoDAO;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<UserInfo> userInfoOpt = Optional.ofNullable(userInfoDAO.loadUserByUsername(username));
UserInfo user = userInfoOpt.orElseThrow(() -> new UsernameNotFoundException("can't not load user by username"));
log.info("根據用戶名:{}查詢用戶成功", user.getUsername());
return user;
}
}
衆所周知,密碼是不能以明文的方式存儲的,貼心的Spring Security天然不會忘記提供加密的功能。PasswordEncoder接口,主要提供2個方法;String encode(CharSequence rawPassword)方法用於加密,由咱們在註冊用戶的時候調用;boolean matches(CharSequence rawPassword, String encodedPassword) 方法用於匹配,登陸驗證時由Spring Security框架調用。
PasswordEncoder.class
若是項目有本身的加解密方式,只須要實現該接口便可,若是沒有能夠嘗試使用Spring提供的BCryptPasswordEncoder密碼加密器。
在這一塊上,咱們能夠自定義與自身業務有關的登陸邏輯判斷,目前沒有這種需求就使用Spring Security提供的默認實現便可。
登陸的後置處理分兩種狀況,第一種是登陸成功的處理,一種是登陸失敗的處理。
Spring Security提供了認證成功處理器接口AuthenticationSuccessHandler,當咱們有一些自定義的業務邏輯,諸如:用戶登陸成功後贈送積分,或者登陸成功後自動跳轉……就能夠經過提供該接口的自定義實現。
AuthenticationSuccessHandler.class
public interface AuthenticationSuccessHandler {
/**
* 默認方法
*/
default void onAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response, FilterChain chain, Authentication authentication)
throws IOException, ServletException{
onAuthenticationSuccess(request, response, authentication);
chain.doFilter(request, response);
}
/**
* 成功後會被調用
*/
void onAuthenticationSuccess(HttpServletRequest request,HttpServletResponse response, Authentication authentication)
throws IOException, ServletException;
}
自定義成功處理器 WebAuthenticationSuccessHandler.class
/**
* 自定義驗證成功處理器
* @author 小奇
*/
@Slf4j
@Component
public class WebAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException {
log.info("登陸成功~~");
// 返回json 可添加自身業務邏輯 如:登陸成功後添加用戶積分等……
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(authentication));
}
}
AuthenticationFailureHandler失敗處理器和成功處理器相似,不作過多的解析,上代碼。
public interface AuthenticationFailureHandler {
/**
* 失敗後調用
*/
void onAuthenticationFailure(HttpServletRequest request,HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException;
}
自定義失敗處理器WebAuthenticationFailureHandler.class
/**
* 自定義驗證失敗處理器
* @author 小奇
*/
@Slf4j
@Component
public class WebAuthenticationFailureHandler implements AuthenticationFailureHandler {
@Autowired
private ObjectMapper objectMapper;
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception)
throws IOException, ServletException {
log.error("登陸失敗");
// 把exception返回給前臺
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(exception));
// 可作其餘業務邏輯,諸如限制天天登陸失敗的次數
}
}
還記得以前咱們提過的Spring Security爲人廣爲詬病的繁瑣配置嗎?自從搭上Spring Boot的列車以後,有了翻天覆地的改變。
下面就來簡單配置一下咱們在上面自定義的一些模塊吧。
/**
* @author kylin
*/
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private WebAuthenticationSuccessHandler successHandler;
@Autowired
private WebAuthenticationFailureHandler failureHandler;
/**
* 密碼加密器,使用spring提供的BCryptPasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* http請求安全配置
*
* @param http
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.antMatchers("/resources/", "/css/", "/about", "/test").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login.html")
.successHandler(successHandler)
.failureHandler(failureHandler)
.permitAll()
.and()
.csrf().disable();
}
}
整個配置就基本完成了,也比較簡單易懂;對一些配置進行基礎的講解
以上內容則爲本文的全內容,文章經過原理探討、動手嘗試逐一展開。若有錯誤之處,請多多指正