天天看點

SpringSecurity - 簡介及簡單配置登入與注冊

Spring Security是一個能夠為基于Spring的企業應用系統提供聲明式的安全通路控制解決方案的安全架構。它提供了一組可以在Spring應用上下文中配置的Bean,充分利用了Spring IoC,DI 和AOP(面向切面程式設計)功能為應用系統提供聲明式的安全通路控制功能,減少了為企業系統安全控制編寫大量重複代碼的工作。 

認證

是為使用者建立一個他所聲明的主體。主體一般是指使用者,裝置或可以在你系統中執行動作的其他系統,即登入過程

授權

指的是一個使用者能否在你的應用中執行某個操作,在到達授權判斷之前,身份的主題已經由身份驗證過程建立了,即目前登入使用者可以具有哪些權限。

依賴引入(Spring相關+SpringSecurity相關+lombok)

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring.version>5.0.2.RELEASE</spring.version>
    <spring.security.version>5.0.1.RELEASE</spring.security.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.6</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>servlet-api</artifactId>
      <version>2.5</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-webmvc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context-support</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>${spring.version}</version>
    </dependency>
    <!-- SpringSecurity -->
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-web</artifactId>
      <version>${spring.security.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-config</artifactId>
      <version>${spring.security.version}</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.security</groupId>
      <artifactId>spring-security-taglibs</artifactId>
      <version>${spring.security.version}</version>
    </dependency>
    <dependency>
      <groupId>javax.servlet</groupId>
      <artifactId>javax.servlet-api</artifactId>
      <version>3.1.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
           

 SpringSecurity核心配置檔案

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans
        xmlns="http://www.springframework.org/schema/security"
        xmlns:beans="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
						http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">

    <!-- 标簽security:http,表示攔截請求的配置。 -->
    <!-- 不攔截靜态資源,比如img、css、js等資源  -->
    <http pattern="/css/**" security="none"/>
    <http pattern="/img/**" security="none"/>
    <http pattern="/js/**" security="none"/>
	<!-- 不攔截的頁面,比如登入界面、注冊界面 -->
    <http pattern="/login.html" security="none"/>
	<http pattern="/register.html" security="none"/>
	<!-- 不攔截的Action,比如注冊 -->
	<http pattern="/add.do" security="none"/>
	
    <!--
        use-expressions:設定是否啟用SpEL表達式,預設值是true
        auto-config:是否使用自帶的登入界面,不寫預設為false
    -->
    <http use-expressions="false">
        <!--
           配置SpringSecurity的攔截路徑(攔截規則)
           - pattern:設定攔截規則
           /*  表示根路徑下所有資源(不包含子路徑)
           /** 表示根路徑下所有資源(包含子路徑)
           - access:設定角色 - 角色命名為ROLE_角色名稱,如ROLE_USER
         -->
        <intercept-url pattern = "/**" access = "ROLE_USER"/>
        <!--
           開啟表單驗證
           username-parameter = "username" 比對前端頁面表單,表單name為username
           password-parameter = "password" 比對前端頁面表單,表單name為password
           login-page:指定的登入頁面
           login-processing-url:登入請求的路徑
           default-target-url:登入成功(身份驗證和授權)後跳轉的位址
           always-use-default-target:是否登入成功之後總是跳轉到default-target-url指定的位址
           authentication-failure-url:登入失敗之後跳轉的位址,小技巧:在位址欄中加一個參數,通過參數的值來判斷是否登入成功,用于前端頁面進行回顯資訊
           authentication-success-handler-ref:default-target-url和always-use-default-target的替代屬性,通過該屬性直接跳到一個排程的Action,由這個Action去進行跳轉(适用場景:當業務需要根據權限跳轉到不同頁面時)
         -->
        <form-login
                login-page = "/login.html"
                login-processing-url = "/login"
                default-target-url = "/index.hmtl"
                always-use-default-target = "true"
                authentication-failure-url = "/login.html?loginError=true"
                authentication-success-handler-ref = "loginSuccessHandler"
        />

        <!-- 不使用csrf校驗,即關閉跨域僞造控制 -->
		<!-- 
			SpringSecurity提供的一個功能,它會随機産生一個token,服務端會驗證
			自定義登入頁是靜态頁面,無法産生token
			可以通過圖形驗證碼的方式實作防止跨域請求僞造
		-->
        <csrf disabled="true"/>

        <!-- 架構頁面不攔截,即html頁面可以使用iframe和frame标簽 -->
        <headers>
            <frame-options policy="SAMEORIGIN" />
        </headers>

        <!--
            登出配置
            logout-url:登出請求的路徑
            logout-success-url:登出成功跳轉的位址
            invalidate-session:是否銷毀Session
         -->
        <logout logout-url="/logout" logout-success-url="/login.html" invalidate-session="true" />

        <!-- 記住我,免密登入,前端表單name為remember-me -->
        <remember-me />


    </http>

    <!-- 配置認證管理器 -->
    <authentication-manager>
        <!--
            認證的提供者
             user-service-ref:自定義使用者認證資訊
         -->
        <authentication-provider user-service-ref="userDetailService">
            <!-- 在認證權限管理器當中指定加密工具類 -->
            <password-encoder ref="passwordEncoder" />
        </authentication-provider>
    </authentication-manager>

    <!-- 自定義使用者認證資訊,自定義認證類需要實作UserDetailsService接口,在這裡做認證和授權 -->
    <beans:bean id="userDetailService" class="pers.liuchengyin.service.UserDetailServiceImpl"></beans:bean>

    <!-- 加密工具類,BCrypt加密方式,比MD5更安全 -->
    <beans:bean id="passwordEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" ></beans:bean>

    <!-- 登入成功,處理登入成功的Action,實作類需要實作AuthenticationSuccessHandler接口 -->
    <beans:bean id="loginSuccessHandler" class="pers.liuchengyin.service.AuthenticationSuccessHandlerImpl" ></beans:bean>

</beans:beans>
           

同源政策

<!-- 架構頁面不攔截,即html頁面可以使用iframe和frame标簽 -->
<headers>
	<frame-options policy="SAMEORIGIN" />
</headers>
           

同源政策限制了從同一個源加載的文檔或腳本如何與來自另一個源的資源進行互動,這是一個用于隔離潛在惡意檔案的重要安全機制。

如果兩個url,協定、位址和端口都相同,我們稱兩個url為同源。 Spring Security下,X-Frame-Options預設為DENY.  如果不修改同源政策,架構頁内将 無法顯示内容。

DENY:浏覽器拒絕目前頁面加載任何Frame頁面 

SAMEORIGIN:frame頁面的位址隻能為同源域名下的頁面 

ALLOW-FROM:origin為允許frame加載的頁面位址。

配置檔案中是通過authentication-success-handler-ref來進行處理登入成功的,是以這裡給一個loginSuccessHandler的示例。該類實作了AuthenticationSuccessHandler接口。這個類的主要作用就是用來登入成功之後處理一些跳轉之前的事情(比如記錄登入日志),或是根據角色權限來進行不同的跳轉。

public class AuthenticationSuccessHandlerImpl implements AuthenticationSuccessHandler {
    // 該方法會在登入成功時調用 - 在這個方法裡可以通過判斷角色的不同,擁有的權限不同,進行不同的跳轉
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
        System.out.println("登入成功");
        // 這裡要進行手動跳轉
        httpServletResponse.sendRedirect("/index.html");
    }
}
           

配置檔案中是自定義認證類來實作登入的,即可以從資料庫中擷取資料。這裡給一個UserDetailServiceImpl的示例,該類實作了UserDetailsService接口。這個類裡有一個loadUserByUsername的方法,它會在登入時被調用。

public class UserDetailServiceImpl implements UserDetailsService {
    /**
     * 該方法會在登入時調用 - 這裡通常就是用來從資料庫中擷取角色和權限的
     * @param username 使用者名
     * @return User對象
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 判斷使用者名是否為空
        if(username == null || "".equals(username)){
            return null;
        }
        // 定義權限的集合
        List<GrantedAuthority> authorityList =  new ArrayList<>();
        // 從資料庫中讀取角色 - 這裡直接寫出來的
        authorityList.add(new SimpleGrantedAuthority("ROLE_USER"));
        // 從資料庫中讀取使用者對象 - 這裡直接寫出來
        Role role = new Role(username,"1234");
        // 判斷role是否為空
        if(role != null){
            // 密碼前拼接上{noop}  表示不加密:{noop}1234
            User user = new User(username,role.getPassword(),authorityList);
            return user;
        }
        return null;
    }
}
           

既然登入有了,那麼注冊是怎麼做的呢?請看下面這個類

public class RoleController {

    @Autowired
    private RoleDao roleDao;

    @Autowired
    private PasswordEncoder passwordEncoder;

    public void add(Role role){
      // 擷取明文密碼
      String password = role.getPassword();
      // 對明文密碼進行加密
      String securityPassword = passwordEncoder.encode(password);
      // 把加密後的密碼存儲到role對象中
      role.setPassword(securityPassword);
      // 調用資料庫添加到資料庫去
      roleDao.add(role);
}
           

那麼它内部是怎麼加密的呢?其實就是通過BCryptpassword這個類生成随機的鹽然後對原密碼混淆。

具體,我這裡就不談了,看下面這篇文章,我也是看了這篇文章才明白的。

https://blog.csdn.net/Leo_songHJ/article/details/83277350