本篇做爲SpringBoot2.1版本的我的開發框架 子章節,請先閱讀SpringBoot2.1版本的我的開發框架再次閱讀本篇文章html
後端項目地址:SpringBoot2.1版本的我的應用開發框架前端
前端項目地址:ywh-vue-adminvue
參考:java
在上一篇文章咱們對spring security有了初步認識之後,咱們這篇主要實現 從數據庫查詢用戶來進行用戶是否具備登錄的認證。git
參考:github
咱們在作用戶登錄以前,咱們先要設計數據庫表,RBAC(Role-Based Access Control,基於角色的訪問控制),就是用戶經過角色與權限進行關聯。簡單地說,一個用戶擁有若干角色,每個角色擁有若干權限。這樣,就構形成「用戶-角色-權限」的受權模型。在這種模型中,用戶與角色之間,角色與權限之間,通常者是多對多的關係。spring
按照我參考的博客中的設計好了五張基礎表,字段的話能夠根據本身的需求變化,生成的sql文件我放到我項目中了。表結構肯定好之後,咱們在項目中把系統用戶的entity、dao、service、以及dao對應的xml都設置好,這裏我就不一一展現代碼了,能夠拿以前的MybatisPlus的自動生成,正好本身又能夠複習一遍以前的,添加好之後能夠如今測試類中確認一下,沒有錯誤再進行下一步。sql
在上一篇筆記中咱們知道security的核心類之一的WebSecurityConfigurerAdapter,咱們能夠在這個配置類中建立本身的用戶,放行哪一個請求,認證什麼請求,在這篇筆記咱們要實現從數據庫中查詢用戶,因此咱們要對如下核心類作實現。數據庫
UserDetails接口是security爲咱們提供的可擴展的用戶信息接口,保存一些非敏感類的信息,在security模塊下entity包中實現此接口類,其實就是一個實體類。後端
package com.ywh.security.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
/** * CreateTime: 2019-01-24 16:10 * ClassName: SecurityUserDetails * Package: com.ywh.security.entity * Describe: * security的用戶詳情類 * * @author YWH */
public class SecurityUserDetails implements UserDetails {
/** * 用戶的密碼 */
private String password;
/** * 用戶的名字 */
private String username;
/** * 用戶狀態,1 表示有效用戶, 0表示無效用戶 */
private Integer state;
/** * 用戶的權限,能夠把用戶的角色信息的集合先放進來,角色表明着權限 */
private Collection<? extends GrantedAuthority> authorties;
public SecurityUserDetails(String password, String username, Integer state, Collection<? extends GrantedAuthority> authorties) {
this.password = password;
this.username = username;
this.state = state;
this.authorties = authorties;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorties;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return username;
}
/** * 指示用戶的賬戶是否已過時。過時的賬戶沒法經過身份驗證。 * @return true若是用戶的賬戶有效(即未過時), false若是再也不有效(即已過時) */
@JsonIgnore
@Override
public boolean isAccountNonExpired() {
return true;
}
/** * 指示用戶是鎖定仍是解鎖。鎖定的用戶沒法進行身份驗證。 * @return true是未鎖定,false是已鎖定 */
@JsonIgnore
@Override
public boolean isAccountNonLocked() {
return true;
}
/** * 指示用戶的憑據(密碼)是否已過時。過時的憑據會阻止身份驗證 * @return true若是用戶的憑證有效(即未過時), false若是再也不有效(即已過時) */
@JsonIgnore
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/** * 指示用戶是啓用仍是禁用。禁用的用戶沒法進行身份驗證。 * @return true用戶已啓用,false用戶已經禁用 */
@JsonIgnore
@Override
public boolean isEnabled() {
return state == 1;
}
}
複製代碼
此接口主要重寫一個方法就行了loadUserByUsername,這個方法主要做用就是經過用戶名查詢用戶的詳細信息,而後放到咱們的UserDetails 實現的子類中,保存在security的SecurityContextHolder中,上一篇咱們經過這個來獲取用戶信息的。
在security模塊中的service/impl中實現此接口,selectByUserName方法就是一個普通的dao接口,在mapper.xml文件中定義好sql語句,因爲很簡單我就不貼了
package com.ywh.security.service.impl;
/** * CreateTime: 2019-01-25 16:39 * ClassName: SecurityUserDetailsServiceImpl * Package: com.ywh.security.service.impl * Describe: * UserDetailService的實現類 * 這個@Primary表示這個類所繼承的接口有多個實現類,當不知道引入哪一個的時候,優先使用@Primary所註解的類 * @author YWH */
@Primary
@Service
public class SecurityUserDetailsServiceImpl implements UserDetailsService {
// 用戶查詢接口
private SysUserDao sysUserDao;
@Autowired
public SecurityUserDetailsServiceImpl(SysUserDao sysUserDao) {
this.sysUserDao = sysUserDao;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SysUserEntity sysUserEntity = sysUserDao.selectByUserName(username);
if(sysUserEntity != null){
// stream java8的新特性,Stream 使用一種相似用 SQL 語句從數據庫查詢數據的直觀方式來提供一種對 Java 集合運算和表達的高階抽象。
// 參考http://www.runoob.com/java/java8-streams.html
List<SimpleGrantedAuthority> collect = sysUserEntity.getRoles().stream().map(SysRoleEntity::getSysRoleName)
.map(SimpleGrantedAuthority::new).collect(Collectors.toList());
return new SecurityUserDetails(sysUserEntity.getSysUserPassword(),sysUserEntity.getSysUserName(),sysUserEntity.getSysUserState(),collect);
}
throw MyExceptionUtil.mxe(String.format("'%s'.這個用戶不存在", username));
}
}
複製代碼
在上一篇中咱們對此類進行了重寫,咱們知道用戶配置是重寫configure(AuthenticationManagerBuilder auth)方法,咱們是經過內存管理器來建立用戶的,這回咱們實現了UserDetailsService接口,咱們就能夠經過這個來查詢用戶並使用。 具體修改以下:
package com.ywh.security.config;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
private UserDetailsService userDetailsService;
@Autowired
public SecurityConfigurer(UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
/** * 用戶信息配置 * @param auth 用戶信息管理器 * @throws Exception 異常信息 */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//修改之前
// auth
// .inMemoryAuthentication()
// .withUser("root")
// .password("root")
// .roles("user")
// .and()
// .passwordEncoder(CharEncoder.getINSTANCE());
//修改之後
auth
.userDetailsService(this.userDetailsService)
.passwordEncoder(passwordEncoder());
}
/** * 密碼加密 * @return 返回加密後的密碼 */
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
複製代碼
重啓項目後咱們能夠看到效果以下,ywh這個用戶是數據庫中的,這個用戶的密碼是我手動調用PasswordEncoder 加密事後放到數據庫中的,密碼都是123。
@Autowired
private PasswordEncoder passwordEncoder;
@Test
public void getOneTest(){
System.out.println("password:" + passwordEncoder.encode("123"));
}
複製代碼
若是輸入一個數據庫中沒有的用戶,則會提示你查無此人
到此步以後咱們完成了從數據庫中查詢用戶登錄的操做,可是登錄頁面是security給咱們默認的頁面。
想使用咱們自定義的頁面,security繼續替咱們認證,在core模塊下的resource文件下建立public目錄後建立咱們自定義的login.html登錄頁面
<!DOCTYPE HTML>
<html>
<head>
<title>登錄頁面</title>
</head>
<body>
<h2>表單登陸</h2>
<form action="/core/login" method="post">
<table>
<tr>
<td>用戶名:</td>
<td>
<input type="text" placeholder="用戶名" name="username" required="required" />
</td>
</tr>
<tr>
<td>密碼:</td>
<td>
<input type="password" placeholder="密碼" name="password" required="required" />
</td>
</tr>
<tr>
<td colspan="2">
<button type="submit">登陸</button>
</td>
</tr>
</table>
</form>
</body>
</html>
複製代碼
要注意form表單中的action屬性,這裏的值要與後面在security配置類中的loginProcessingUrl("/login")相同,我寫成/core/login是由於我配置了context-path,若是你沒配置不用寫/core前綴
訪問的index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>測試</title>
</head>
<body>
登錄了!!!
</body>
</html>
複製代碼
修改security配置類的configure(HttpSecurity httpSecurity)方法
/** * 配置如何經過攔截器保護咱們的請求,哪些能經過哪些不能經過,容許對特定的http請求基於安全考慮進行配置 * @param httpSecurity http * @throws Exception 異常 */
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
// 暫時禁用csrc不然沒法提交
.csrf().disable()
// 設置最多一個用戶登陸,若是第二個用戶登錄則第一用戶被踢出,並跳轉到登錄頁面
.sessionManagement().maximumSessions(1).expiredUrl("/login.html");
httpSecurity
// 開始認證
.authorizeRequests()
// 對靜態文件和登錄頁面放行
.antMatchers("/static/**").permitAll()
.antMatchers("/auth/**").permitAll()
.antMatchers("/login.html").permitAll()
// 其餘請求須要認證登錄
.anyRequest().authenticated();
httpSecurity
// 表單登錄
.formLogin()
// 設置跳轉的登錄頁面
.loginPage("/login.html")
// .failureUrl("/auth/login?error") 設置若是出錯跳轉到哪一個頁面
// security默認使用的就是login路徑認證,若是想使用自定義自行修改就能夠了
.loginProcessingUrl("/login")
// 若是直接訪問登陸頁面,則登陸成功後重定向到這個頁面,不然跳轉到以前想要訪問的頁面
.defaultSuccessUrl("/index.html");
}
複製代碼
loginPage()中也能夠填寫接口路徑,經過接口來返回登錄頁面;經過以上代碼實現了咱們本身定義的登錄頁面,security會爲咱們攔截並認證的,只是建立了兩個頁面和增長了兩個屬性。
想要自定義實現登錄成功的邏輯,能夠配置successHandler()方法,要實現的接口爲AuthenticationSuccessHandler,重寫其中的方法爲onAuthenticationSuccess()。
想要實現自定義退出功能的邏輯,須要實現AuthenticationFailureHandler 接口,重寫其中的onAuthenticationFailure方法。或者在配置中的方法中寫匿名內部類。
而關於security中的退出,也有給咱們默認實現,經過「/logout」就能夠實現退出功能了,在index.html界面添加一個 a 標籤便可。
<a href="/core/logout">退出</a>
複製代碼
再次提示我這裏寫/core是由於我配置了context-path,若是你沒有配置直接寫/logout便可,默認security機制會進行以下操做:
固然咱們也能夠自定義退出,在configure(HttpSecurity httpSecurity)方法中添加如下內容,這裏我只實現了最簡單的操做
httpSecurity
// 登出
.logout()
// 登出處理,使用security默認的logout,也能夠自定義路徑,實現便可
.logoutUrl("/logout")
// 登出成功後跳轉到哪一個頁面
.logoutSuccessUrl("/login.html")
.logoutSuccessHandler((request, response, authentication) -> {
//登出成功處理函數
System.out.println("logout success");
response.sendRedirect("/core/login.html");
})
.addLogoutHandler((request, response, authentication) ->{
//登出處理函數
System.out.println("logout------");
})
// 清理Session
.invalidateHttpSession(true);
複製代碼
關於security上面咱們實現了最基本的功能,它還能夠作更多的事情,好比記住個人密碼,第三方登陸等等。