SpringBoot中使用SpringSecurity實現認證和受權(入門)

簡介

SpringSecurity 是Spring項目組中用來提供安全認證(authentication)和受權(authorization)服務的框架。所謂的認證通俗的說就是判斷正在操做的用戶和密碼是否匹配,而受權就是控制用戶能作什麼操做,也就是能幹什麼能看到什麼。html

環境搭建

以idea開發工具爲例,模板引擎使用的是thymeleaf
pom.xmljava

<properties>
		<java.version>1.8</java.version>
		<thymeleaf.version>3.0.9.RELEASE</thymeleaf.version>
		<thymeleaf-layout-dialect.version>2.3.0</thymeleaf-layout-dialect.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
複製代碼

添加一些模板文件,這些拷貝便可
KungfuController.javamysql

@Controller
public class KungfuController {
	private final String PREFIX = "pages/";
	/** * 歡迎頁 * @return */
	@GetMapping("/")
	public String index() {
		return "welcome";
	}
	
	/** * 登錄頁 * @return */
	@GetMapping("/userlogin")
	public String loginPage() {
		return PREFIX+"login";
	}
	
	
	/** * level1頁面映射 * @param path * @return */
	@GetMapping("/level1/{path}")
	public String level1(@PathVariable("path")String path) {
		return PREFIX+"level1/"+path;
	}
	
	/** * level2頁面映射 * @param path * @return */
	@GetMapping("/level2/{path}")
	public String level2(@PathVariable("path")String path) {
		return PREFIX+"level2/"+path;
	}
	
	/** * level3頁面映射 * @param path * @return */
	@GetMapping("/level3/{path}")
	public String level3(@PathVariable("path")String path) {
		return PREFIX+"level3/"+path;
	}
       /** * 登陸失敗頁 * @return */
	@GetMapping("/loginError")
	public String loginError() {
		return "loginError";
	}
}
複製代碼

level1/1.htmlweb

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>羅漢拳</h1>
	<p>羅漢拳站當央,打起來不要慌</p>
</body>
</html>
複製代碼

level1/2.html算法

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>武當長拳</h1>
	<p>長一點在長一點</p>
</body>
</html>
複製代碼

level1/3.htmlspring

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>全真劍法</h1>
	<p>全都是真的</p>
</body>
</html>
複製代碼

level2/1.htmlsql

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>太極拳</h1>
	<p>
	       一個西瓜圓又圓 劈它一刀成兩半 你一半來 給你你不要 給他他不收 那就不給 把兩人攆走 他們不走你走 走啦,一揮手,傷自尊
                  不買西瓜別纏我,緩慢糾纏様 兩人纏我賴皮,手慢動做左右揮動 看我厲害,轉頭緩步拍蒼蠅狀 拍死了,手抱西瓜狀+奧特曼十字手+廣播操準備運動的站立
    </p>
</body>
</html>
複製代碼

level2/2.html數據庫

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>七傷拳</h1>
	<p>練這拳的人全都死了</p>
</body>
</html>
複製代碼

level2/3.html瀏覽器

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>梯雲縱</h1>
	<p>踩本身的腳往上跳</p>
</body>
</html>
複製代碼

level3/1.html安全

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>葵花寶典</h1>
	<p>欲練神功,揮刀自宮</p>
</body>
</html>
複製代碼

level3/2.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>龜派氣功</h1>
	<p>龜-派-氣-功-波</p>
</body>
</html>
複製代碼

level3/3.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
	<a th:href="@{/}">返回</a>
	<h1>獨孤九劍</h1>
	<p>欲練此劍,必先犯賤</p>
</body>
</html>
複製代碼

login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
	<h1 align="center">歡迎登錄武林祕籍管理系統</h1>
	<hr>
	<div align="center">
		<form action="" method="post">
			用戶名:<input name=""/><br>
			密碼:<input name=""><br/>
			<input type="submit" value="登錄">
		</form>
	</div>
</body>
</html>
複製代碼

loginError.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <h1>登陸錯誤</h1>
</body>
</html>
複製代碼

welcome.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1 align="center">歡迎光臨武林祕籍管理系統</h1>
<h2 align="center">遊客您好,若是想查看武林祕籍 <a th:href="@{/login}">請登陸</a></h2>
<hr>

<h3>普通武功祕籍</h3>
<ul>
	<li><a th:href="@{/level1/1}">羅漢拳</a></li>
	<li><a th:href="@{/level1/2}">武當長拳</a></li>
	<li><a th:href="@{/level1/3}">全真劍法</a></li>
</ul>

<h3>高級武功祕籍</h3>
<ul>
	<li><a th:href="@{/level2/1}">太極拳</a></li>
	<li><a th:href="@{/level2/2}">七傷拳</a></li>
	<li><a th:href="@{/level2/3}">梯雲縱</a></li>
</ul>

<h3>絕世武功祕籍</h3>
<ul>
	<li><a th:href="@{/level3/1}">葵花寶典</a></li>
	<li><a th:href="@{/level3/2}">龜派氣功</a></li>
	<li><a th:href="@{/level3/3}">獨孤九劍</a></li>
</ul>

</body>
</html>
複製代碼

項目的目錄結構以下:

而後咱們能夠先啓動項目,訪問下項目地址,檢查下環境是否正常:

這個時候咱們能夠點擊上面的每一個功夫連接到每一個功夫頁面中。

內存中配置用戶信息實現權限與受權

首先引入SpringSecurity的依賴

<dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
複製代碼

增長一個配置類config/MySecurityConfig.java,須要繼承WebSecurityConfigurerAdapter

@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Override
    protected void configure(HttpSecurity http) throws Exception {
        //super.configure(http);
        //定製請求的受權規則
        http.authorizeRequests().antMatchers("/").permitAll()//訪問/路徑不須要擁有任何角色
                .antMatchers("/level1/**").hasRole("VIP1")//訪問此路徑須要VIP1角色
                .antMatchers("/level2/**").hasRole("VIP2")
                .antMatchers("/level3/**").hasRole("VIP3");
    }
}
複製代碼

這時候咱們再重啓,訪問每一個須要角色的路徑時就會被拒絕訪問

這時候須要咱們添加自動登陸的功能(在上面的configure(HttpSecurityhttp)方法中添加),會給咱們生成一個自動登陸到頁面,發送指定的請求/login會來到登陸頁面,若是登陸失敗會重定向到/login?error表示登陸失敗

http.formLogin();
複製代碼

若是沒有登陸訪問須要權限的url就會來到這個登陸頁面。
具體的登陸信息咱們能夠在類中定義,也能夠在數據庫中定義。
定義認證規則
首先重寫configure(AuthenticationManagerBuilder auth)方法

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       //這裏定義的是從內存中取出用戶名和密碼,後面的是數據庫中取出來驗證
        auth.inMemoryAuthentication()
                .withUser("zhangsan").password("123456").roles("VIP1","VIP2")//分別表示登陸名,登陸密碼,此用戶擁有的角色
                .and()
                .withUser("lisi").password("123456").roles("VIP2","VIP3")
                .and()
                .withUser("wangwu").password("123456").roles("VIP1","VIP3");
    }
複製代碼

定義完認證規則以後咱們能夠重啓項目,再次訪問,這時候訪問須要角色的頁面時會重定向到登陸頁面,這時候咱們輸入上面定義的任一用戶登陸信息,登陸成功後便可訪問對應角色的頁面。
接下來咱們能夠加上註銷功能
MySecurityConfig配置類configure(HttpSecurity http)方法中加入自動註銷功能

//註銷成功會默認返回/login?logout
    http.logout().logoutSuccessUrl("/");//指定註銷成功之後來到首頁
    //前臺頁面post請求訪問/logout表示用戶註銷,而且清空session
複製代碼

前臺頁面welcome.html加上退出表單

<form th:action="@{/logout}" method="post"><!-- 方法須要時post -->
		<input type="submit" value="註銷"/>
	</form>
複製代碼

這時候咱們再次重啓項目訪問項目地址登陸以後就能夠註銷了,註銷以後就不能訪問任何須要角色的頁面了。

接下來再增長認證受權在前臺頁面的應用
前臺頁面代碼以下

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4">
<head>
	<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
	<title>Insert title here</title>
</head>
<body>
<h1 align="center">歡迎光臨武林祕籍管理系統</h1>
<div sec:authorize="!isAuthenticated()"><!-- 若是沒認證 -->
	<h2 align="center">遊客您好,若是想查看武林祕籍 <a th:href="@{/login}">請登陸</a></h2>
</div>
<div sec:authorize="isAuthenticated()"><!-- 若是認證認證了 -->
    <!-- 這裏能夠讀取到登陸的用戶名和擁有的角色 -->
	<h2><span sec:authentication="name"></span>,您好,您的角色有:
		<span sec:authentication="principal.authorities"></span></h2>
	<form th:action="@{/logout}" method="post"><!-- 方法須要時post -->
		<input type="submit" value="註銷"/>
	</form>
</div>
<hr>

<div sec:authorize="hasRole('VIP1')"><!-- 對擁有VIP1角色的用戶顯示 -->
	<h3>普通武功祕籍</h3>
	<ul>
		<li><a th:href="@{/level1/1}">羅漢拳</a></li>
		<li><a th:href="@{/level1/2}">武當長拳</a></li>
		<li><a th:href="@{/level1/3}">全真劍法</a></li>
	</ul>

</div>

<div sec:authorize="hasRole('VIP2')"><!-- 對擁有VIP2角色的用戶顯示 -->
	<h3>高級武功祕籍</h3>
	<ul>
		<li><a th:href="@{/level2/1}">太極拳</a></li>
		<li><a th:href="@{/level2/2}">七傷拳</a></li>
		<li><a th:href="@{/level2/3}">梯雲縱</a></li>
	</ul>

</div>

<div sec:authorize="hasRole('VIP3')"><!-- 對擁有VIP3角色的用戶顯示 -->
	<h3>絕世武功祕籍</h3>
	<ul>
		<li><a th:href="@{/level3/1}">葵花寶典</a></li>
		<li><a th:href="@{/level3/2}">龜派氣功</a></li>
		<li><a th:href="@{/level3/3}">獨孤九劍</a></li>
	</ul>
</div>
</body>
</html>
複製代碼

咱們還須要在pom文件中加一個依賴:SpringSecurity和thymeleaf的整合模塊

<properties><!-- 在properties控制下版本 -->
    	<thymeleaf-extras-springsecurity4.version>3.0.2.RELEASE</thymeleaf-extras-springsecurity4.version>
   </properties
    <dependency>
        <groupId>org.thymeleaf.extras</groupId>
        <artifactId>thymeleaf-extras-springsecurity4</artifactId>
    </dependency>
複製代碼

這時候咱們再次訪問登陸就能夠看到頁面上所登陸用戶的不一樣展現的差別了

記住登陸狀態
此時咱們登陸以後關閉瀏覽器再次啓動後,訪問項目須要從新登陸。若是可以記住登陸狀態,之後只須要登陸一次,只要不註銷,重啓瀏覽器訪問也依然是登陸狀態。
開啓記住我功能
在配置類的configure(HttpSecurity http)方法中添加

http.rememberMe();
複製代碼

當咱們添加上面的配置時,登陸頁面就會自動加上一個記住的按鈕。SpringSecurity是將登陸信息存儲到cookie中,默認存在的時間爲14天。可是若是點擊註銷,就會當即清除這個cookie信息。
指定本身的登陸頁面,

  1. 添加login.html的登陸請求/userlogin,並綁定input輸入框
    <form th:action="@{/userlogin}" method="post">
        用戶名:<input name="user"/><br>
        密碼:<input name="pwd"><br/>
        <input type="checkbox" name="remeber">記住我 <br/>
        <input type="submit" value="登錄">
    </form>
    複製代碼
  2. 修改默認登陸請求
http.formLogin().usernameParameter("user").passwordParameter("pwd")//指定登陸input標籤名稱
            .loginPage("/userlogin").failureUrl("/loginError");//指定定製的登陸請求和登陸失敗後的請求
            http.rememberMe().rememberMeParameter("remeber");//添加記住標籤的參數名稱
複製代碼
  1. 將welcome.html頁面的登陸請求更換爲/userlogin
<a th:href="@{/userlogin}">請登陸</a>
複製代碼

這時候訪問的時候就會來到自定義的登陸頁面

數據庫用戶信息實現權限與受權

建立幾張表以下:

CREATE TABLE USER(
	user_id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(30),
	PASSWORD VARCHAR(30)
)
CREATE TABLE role(
	role_id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(30)
)
CREATE TABLE user_role(
	role_id INT,
	user_id INT
)
-- 插入數據 這裏使用的是MD5加密算法,密碼是123456
INSERT INTO role(NAME)VALUES('ROLE_VIP1');
INSERT INTO role(NAME)VALUES('ROLE_VIP2');
INSERT INTO role(NAME)VALUES('ROLE_VIP3');
INSERT INTO USER(NAME,PASSWORD)VALUES('zhangsan','a3caed36f0fe5a01e5f144db8927235e');
INSERT INTO USER(NAME,PASSWORD)VALUES('list','a3caed36f0fe5a01e5f144db8927235e');
INSERT INTO USER(NAME,PASSWORD)VALUES('wangwu','a3caed36f0fe5a01e5f144db8927235e');
INSERT INTO user_role(role_id,user_id)VALUES(1,1);
INSERT INTO user_role(role_id,user_id)VALUES(2,1);
INSERT INTO user_role(role_id,user_id)VALUES(3,2);
INSERT INTO user_role(role_id,user_id)VALUES(2,2);
INSERT INTO user_role(role_id,user_id)VALUES(3,3);

複製代碼

引入pom文件

<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.4</version>
		</dependency>

		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<scope>runtime</scope>
		</dependency>

		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.1.10</version>
		</dependency>
複製代碼

加入application.properties配置信息

server.port=8085
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=123456
複製代碼

編寫實體類UserInfo

public class UserInfo implements Serializable {
    private Integer userId;
    private String name;
    private String password;
    private List<Role> roles;
    //...
}
複製代碼

編寫實體類Role

public class Role implements Serializable {
    private Integer roleId;
    private String name;
    //...
}
複製代碼

編寫dao
UserMapper

public interface UserMapper {

    @Select("select * from user where name=#{username}")
    @Results({
            @Result(id = true, property = "userId", column = "user_id"),
            @Result(property = "name", column = "NAME"),
            @Result(property = "password", column = "PASSWORD"),
            @Result(property = "roles",column = "user_id",javaType = java.util.List.class,many = @Many(select = "com.example.mapper.RoleMapper.findRoleByUserId"))
    })
    UserInfo findByUsername(String username);
}
複製代碼

RoleMapper

public interface RoleMapper {

    @Select("select * from role where role_id in (select role_id from user_role where user_id=#{userId})")
    @Results({
            @Result(id = true, property = "roleId", column = "role_id"),
            @Result(property = "name", column = "name")
    })
    Role findRoleByUserId(Integer userId);
}
複製代碼

編寫service UserService須要繼承UserDetailsService

public interface UserService extends UserDetailsService {//繼承UserDetailsService

    UserInfo findByUsername(String username);
}
複製代碼

實現類

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserInfo findByUsername(String username) {
        return userMapper.findByUsername(username);
    }

    /** * * @param username * @return * @throws UsernameNotFoundException */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        UserInfo userInfo = null;

        try {
            userInfo = userMapper.findByUsername(username);
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println(userInfo);
        User user = new User(userInfo.getName(),userInfo.getPassword(),getAuthority(userInfo.getRoles()));
        return user;
    }

    public List<SimpleGrantedAuthority> getAuthority(List<Role> roles) {
        List<SimpleGrantedAuthority> list = new ArrayList<>();
        for (Role role : roles) {
            list.add(new SimpleGrantedAuthority(role.getName()));
        }
        return list;
    }
}

複製代碼

配置類MySecurityConfig中須要修改configure(AuthenticationManagerBuilder auth)方法

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
       auth.userDetailsService(customUserService())//user Details Service驗證
             .passwordEncoder(new PasswordEncoder() {
            @Override
            public String encode(CharSequence rawPassword) {//指定下加密規則
                return MD5Utils.encode((String) rawPassword);
            }

            @Override//匹配接收到的密碼是否和數據庫中查詢到的一致
            public boolean matches(CharSequence rawPassword, String encodedPassword) {
                return encodedPassword.equals(MD5Utils.encode((String)rawPassword));
            }
        });
}
複製代碼

最後加密類

public class MD5Utils {

    private static final String SALT = "tamboo";

    public static String encode(String password) {
        password = password + SALT;
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        char[] charArray = password.toCharArray();
        byte[] byteArray = new byte[charArray.length];

        for (int i = 0; i < charArray.length; i++)
            byteArray[i] = (byte) charArray[i];
        byte[] md5Bytes = md5.digest(byteArray);
        StringBuffer hexValue = new StringBuffer();
        for (int i = 0; i < md5Bytes.length; i++) {
            int val = ((int) md5Bytes[i]) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }

            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }
}
複製代碼

如今能夠訪問項目地址,登陸的密碼是123456

第一次寫博客,水平有限,思路也有問題,語言組織的也有問題,但願諒解

相關文章
相關標籤/搜索