1. 程式人生 > >Spring Security 簡單實現使用者登陸

Spring Security 簡單實現使用者登陸

Spring Security 實現使用者登陸

本文僅介紹Spring Security的基本使用。

Spring Security簡介

Spring Security 基於 Spring 框架,提供了一套 Web 應用安全性的完整解決方案。Web 應用的安全性包括使用者認證(Authentication)和使用者授權(Authorization)兩個部分。

  • 認證(Authentication):“認證”是建立主體(principal)的過程。“主體”通常是指可以在您的應用程式中執行操作的使用者、裝置或其他系統。
  • 授權(Authorization):或稱為“訪問控制(Access-control)”,“授權”是指決定是否允許主體在應用程式中執行操作。

身份驗證技術主要有:

身份驗證 . . .
HTTP BASIC 單點登陸
HTTP Digest Remember-Me
HTTP X.509 匿名身份驗證
LDAP Run-as
基於表單的認證 JAAS
OpenID JavaEE容器認證

Spring Security 配置

完整工程

build.gradle

關鍵程式碼:

dependencies {
    //SpringBoot必要元件和測試元件
	implementation('org.springframework.boot:spring-boot-starter-web')
	testImplementation('org.springframework.boot:spring-boot-starter-test')
	//thymeleaf 模板引擎
	compile('org.springframework.boot:spring-boot-starter-thymeleaf')
	//Spring Data JPA 持久層支援
compile('org.springframework.boot:spring-boot-starter-data-jpa') //Mysql 連線驅動 compile('mysql:mysql-connector-java:8.0.11') //H2 記憶體資料庫 runtime('com.h2database:h2:1.4.193') //Spring Security 許可權管理 compile('org.springframework.boot:spring-boot-starter-security') //Thymeleaf Spring Security 對Thymeleaf的支援 compile('org.thymeleaf.extras:thymeleaf-extras-springsecurity4:3.0.2.RELEASE') }

完整程式碼:HERE

SecurityConfig.java

SecurityConfig.java為配置類,繼承自org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter類。

用於自定義一些配置。

package com.example.demo.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	/**
	 * 自定義許可權配置
	 */
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests().antMatchers("/css/**", "/js/**", "/fonts/**", "/index").permitAll()// 都可以訪問
				.antMatchers("/users/**").hasRole("ADMIN")// 需要相應角色許可權才能訪問
				.and().formLogin()// 基於Form表單驗證
				.loginPage("/login").failureUrl("/login-error");// 自定義登陸介面
		http.csrf().disable();// 禁用security的csrf功能
	}
	/**
	 * 使用者認證
	 * 新增使用者名稱為admin密碼為12345的ADMIN許可權使用者
	 * @param auth
	 * @throws Exception
	 */
	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
		auth.inMemoryAuthentication()// 認證資訊儲存在記憶體中
				.withUser("admin").password("12345").roles("ADMIN");
	}
}

這裡注意:

http.csrf().disable();// 禁用security的csrf功能

這句是為了使RESTful正常使用。

這裡設定了/css/**/js/**/fonts/**/index是都可以訪問且不涉及許可權的,但需要ADMIN賬戶才能訪問。且登陸基於Form表單驗證,登陸介面和登陸錯誤返回頁面分別為/login/login-error

並將賬號資訊注入到記憶體中,即使用者名稱為admin密碼為12345ADMIN許可權使用者,此例未將賬號資訊儲存到持久層內。

MainController.java

@Controller
public class MainController {
	@GetMapping("/")
	public String root() {
		return "redirect:/index";
	}
	@GetMapping("/index")
	public String index() {
		return "index";
	}
	@GetMapping("/login")
	public String login() {
		return "login";
	}
	@GetMapping("/login-error")
	public String loginError(Model model) {
		model.addAttribute("loginError",true);
		model.addAttribute("errorMsg", "登陸失敗,使用者名稱或密碼錯誤!");
		return "login";
	}
}

完整程式碼:HERE

前端框架

fragments部分

header.html會根據使用者是否登陸顯示不同的內容。

<!-- 登陸判斷 -->
<div sec:authorize="isAuthenticated()" class="row">
	<ul class="nav navbar-nav navbar-right">
		<li><a><span class="nav-link" sec:authentication="name"></span></a></li>
		<li><a href="javascript:doPost()" class="btn btn-outline-success">退出<span class="sr-only">(current)</span></a></li>
	</ul>
</div>
<div sec:authorize="isAnonymous()">
	<ul class="nav navbar-nav navbar-right">
		<li><a href="/login" th:href="@{~/login}" class="btn btn-outline-success">登陸</a></li>
	</ul>
</div>

footer.htmljavascript:doPost()函式也根據使用者是否登陸顯示出來。

<div sec:authorize="isAuthenticated()">
	<script>
		function doPost() {  // 登出函式
			var myForm = document.createElement("form");     
			myForm.method = "post";
			myForm.action = "/logout";    
			document.body.appendChild(myForm);   
			myForm.submit(); 
			document.body.removeChild(myForm);  // 提交後移除建立的form
		}
	</script>
</div>

此處用JavaScript建立一個表單,要想正常工作須在前面配置檔案初加上程式碼:

http.csrf().disable();// 禁用security的csrf功能

來禁用security的csrf功能。

templates模板

index.html根據使用者是否登陸顯示不同的頁面。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head th:replace="~{fragments/header::header}">
</head>
<body>
	<div class="container">
		<div class="jumbotron">
			<div sec:authorize="isAuthenticated()">
				<p>已有使用者登陸</p>
				<p>
					登陸的使用者為:<span sec:authentication="name"></span>
				</p>
				<p>
					使用者角色為:<span sec:authentication="principal.authorities"></span>
				</p>
			</div>
			<div sec:authorize="isAnonymous()">
				<p>未有使用者登陸</p>
			</div>
		</div>
	</div>
	<div th:replace="~{fragments/footer::footer}"></div>
</body>
</html>

login.html則是根據一個form表單來提交賬號密碼。

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org"
	xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
	xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head th:replace="~{fragments/header::header}">
</head>
<body>
	<!-- Page content -->
	<div class="container">
		<div class="jumbotron">
			<form th:action="@{~/login}" method="post">
				<h2>請登入</h2>
				<div class="form-group col-md-5">
					<label for="username" class="col-form-label">賬號</label> <input
						type="text" class="form-control" id="username" name="username"
						maxlength="50" placeholder="請輸入賬號">
				</div>
				<div class="form-group col-md-5">
					<label for="password" class="col-form-label">密碼</label> <input
						type="password" class="form-control" id="password" name="password"
						maxlength="30" placeholder="請輸入密碼">
				</div>
				<div class="form-group col-md-5">
					<button type="submit" class="btn btn-primary">登陸</button>
				</div>
				<div class="col-md-5" th:if="${loginError}">
					<p class="label-error" th:text="${errorMsg}"></p>
				</div>
			</form>
			<br><br><br><br><br><br>
		</div>
	</div>
	<div th:replace="~{fragments/footer::footer}"></div>
</body>
</html>

完成後介面:

未有使用者登陸時

登陸介面

已有使用者登陸

完整專案地址:HRER