SpringBoot2.1版本的我的應用開發框架 - 使用SpringSecurity管理咱們的訪問權限2

本篇做爲SpringBoot2.1版本的我的開發框架 子章節,請先閱讀SpringBoot2.1版本的我的開發框架再次閱讀本篇文章html

後端項目地址:SpringBoot2.1版本的我的應用開發框架前端

前端項目地址:ywh-vue-adminvue

參考:java

在上一篇文章咱們對spring security有了初步認識之後,咱們這篇主要實現 從數據庫查詢用戶來進行用戶是否具備登錄的認證。git

數據庫表的設計

參考:github

  • RBAC權限管理 這篇文章講的很是詳細,只不過有點久遠,12年我還在上高一。。。

咱們在作用戶登錄以前,咱們先要設計數據庫表,RBAC(Role-Based Access Control,基於角色的訪問控制),就是用戶經過角色與權限進行關聯。簡單地說,一個用戶擁有若干角色,每個角色擁有若干權限。這樣,就構形成「用戶-角色-權限」的受權模型。在這種模型中,用戶與角色之間,角色與權限之間,通常者是多對多的關係。spring

在這裏插入圖片描述
按照我參考的博客中的設計好了五張基礎表,字段的話能夠根據本身的需求變化,生成的sql文件我放到我項目中了。

表結構肯定好之後,咱們在項目中把系統用戶的entity、dao、service、以及dao對應的xml都設置好,這裏我就不一一展現代碼了,能夠拿以前的MybatisPlus的自動生成,正好本身又能夠複習一遍以前的,添加好之後能夠如今測試類中確認一下,沒有錯誤再進行下一步。sql

security核心類

在上一篇筆記中咱們知道security的核心類之一的WebSecurityConfigurerAdapter,咱們能夠在這個配置類中建立本身的用戶,放行哪一個請求,認證什麼請求,在這篇筆記咱們要實現從數據庫中查詢用戶,因此咱們要對如下核心類作實現。數據庫

  • UserDetails接口
  • UserDetailsService接口

UserDetails接口

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;
    }
}

複製代碼

UserDetailsService接口

此接口主要重寫一個方法就行了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));
    }
}

複製代碼

修改WebSecurityConfigurerAdapter實現類

在上一篇中咱們對此類進行了重寫,咱們知道用戶配置是重寫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"));
}
複製代碼

在這裏插入圖片描述
若是輸入一個數據庫中沒有的用戶,則會提示你查無此人
在這裏插入圖片描述

自定義登錄

參考:Spring Security自定義用戶登錄認證

到此步以後咱們完成了從數據庫中查詢用戶登錄的操做,可是登錄頁面是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機制會進行以下操做:

  • 使HttpSession無效
  • 清理記住密碼
  • 清理SecurityContextHolder
  • 重定向/login?logout

固然咱們也能夠自定義退出,在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上面咱們實現了最基本的功能,它還能夠作更多的事情,好比記住個人密碼,第三方登陸等等。

相關文章
相關標籤/搜索