第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

Spring Boot 安全管理

1. 前言

安全管理是軟件系統(tǒng)必不可少的的功能。根據(jù)經(jīng)典的“墨菲定律”——凡是可能,總會(huì)發(fā)生。如果系統(tǒng)存在安全隱患,最終必然會(huì)出現(xiàn)問(wèn)題。

本節(jié)就來(lái)演示下,如何使用 Spring Boot + Spring Security 開(kāi)發(fā)前后端分離的權(quán)限管理功能。

2. Spring Security 用法簡(jiǎn)介

作為一個(gè)知名的安全管理框架, Spring Security 對(duì)安全管理功能的封裝已經(jīng)非常完整了。

我們?cè)谑褂?Spring Security 時(shí),只需要從配置文件或者數(shù)據(jù)庫(kù)中,把用戶(hù)、權(quán)限相關(guān)的信息取出來(lái)。然后通過(guò)配置類(lèi)方法告訴 Spring Security , Spring Security 就能自動(dòng)實(shí)現(xiàn)認(rèn)證、授權(quán)等安全管理操作了。

  • 系統(tǒng)初始化時(shí),告訴 Spring Security 訪問(wèn)路徑所需要的對(duì)應(yīng)權(quán)限。
  • 登錄時(shí),告訴 Spring Security 真實(shí)用戶(hù)名和密碼。
  • 登錄成功時(shí),告訴 Spring Security 當(dāng)前用戶(hù)具備的權(quán)限。
  • 用戶(hù)訪問(wèn)接口時(shí),Spring Security 已經(jīng)知道用戶(hù)具備的權(quán)限,也知道訪問(wèn)路徑需要的對(duì)應(yīng)權(quán)限,所以自動(dòng)判斷能否訪問(wèn)。

3. 數(shù)據(jù)庫(kù)模塊實(shí)現(xiàn)

3.1 定義表結(jié)構(gòu)

需要 4 張表:

  • 用戶(hù)表 user:保存用戶(hù)名、密碼,及用戶(hù)擁有的角色 id 。
  • 角色表 role :保存角色 id 與角色名稱(chēng)。
  • 角色權(quán)限表 roleapi:保存角色擁有的權(quán)限信息。
  • 權(quán)限表 api:保存權(quán)限信息,在前后端分離的項(xiàng)目中,權(quán)限指的是控制器中的開(kāi)放接口。

具體表結(jié)構(gòu)如下,需要注意的是 api 表中的 path 字段表示接口的訪問(wèn)路徑,另外所有的 id 都是自增主鍵。

數(shù)據(jù)庫(kù)表結(jié)構(gòu)

3.2 構(gòu)造測(cè)試數(shù)據(jù)

執(zhí)行如下 SQL 語(yǔ)句插入測(cè)試數(shù)據(jù),下面的語(yǔ)句指定了 admin 用戶(hù)可以訪問(wèn) viewGoods 和 addGoods 接口,而 guest 用戶(hù)只能訪問(wèn) viewGoods 接口。

實(shí)例:

-- 用戶(hù)
INSERT INTO `user` VALUES (1, 'admin', '$2a$10$D0OvhHj2Lh92rNey1EFmM.OqltxhH1vZA8mDpxz7jEofDEqLRplQy', 1);
INSERT INTO `user` VALUES (2, 'guest', '$2a$10$D0OvhHj2Lh92rNey1EFmM.OqltxhH1vZA8mDpxz7jEofDEqLRplQy', 2);
-- 角色
INSERT INTO `role` VALUES (1, '管理員');
INSERT INTO `role` VALUES (2, '游客');
-- 角色權(quán)限
INSERT INTO `roleapi` VALUES (1, 1, 1);
INSERT INTO `roleapi` VALUES (2, 1, 2);
INSERT INTO `roleapi` VALUES (3, 2, 1);
-- 權(quán)限
INSERT INTO `api` VALUES (1, 'viewGoods');
INSERT INTO `api` VALUES (2, 'addGoods');

Tips:用戶(hù)密碼是 123 加密后的值,大家了解即可,稍后再進(jìn)行解釋。

4. Spring Boot 后端實(shí)現(xiàn)

我們新建一個(gè) Spring Boot 項(xiàng)目,并利用 Spring Security 實(shí)現(xiàn)安全管理功能。

4.1 使用 Spring Initializr 創(chuàng)建項(xiàng)目

Spring Boot 版本選擇 2.2.5 ,Group 為 com.imooc , Artifact 為 spring-boot-security,生成項(xiàng)目后導(dǎo)入 Eclipse 開(kāi)發(fā)環(huán)境。

4.2 引入項(xiàng)目依賴(lài)

我們引入 Web 項(xiàng)目依賴(lài)、安全管理依賴(lài),由于要訪問(wèn)數(shù)據(jù)庫(kù)所以引入 JDBC 和 MySQL 依賴(lài)。

實(shí)例:

		<!-- Web項(xiàng)目依賴(lài) -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<!-- 安全管理依賴(lài) -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<!-- JDBC -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-jdbc</artifactId>
		</dependency>
		<!-- MySQL -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>

4.3 定義數(shù)據(jù)對(duì)象

安全管理,肯定需要從數(shù)據(jù)庫(kù)中讀取用戶(hù)信息,以便判斷用戶(hù)登錄名、密碼是否正確,所以需要定義用戶(hù)數(shù)據(jù)對(duì)象。

實(shí)例:

public class UserDo {
	private Long id;
	private String username;
	private String password;
	private String roleId;
	// 省略 get set
}

4.4 開(kāi)發(fā)數(shù)據(jù)訪問(wèn)類(lèi)

系統(tǒng)初始化時(shí),告訴 Spring Security 訪問(wèn)路徑所需要的對(duì)應(yīng)權(quán)限,所以我們開(kāi)發(fā)從數(shù)據(jù)庫(kù)獲取權(quán)限列表的方法。

實(shí)例:

@Repository
public class ApiDao {
	@Autowired
	private JdbcTemplate jdbcTemplate;

	/**
	 * 獲取所有api
	 */
	public List<String> getApiPaths() {
		String sql = "select path from api";
		return jdbcTemplate.queryForList(sql, String.class);
	}
}

登錄時(shí),告訴 Spring Security 真實(shí)用戶(hù)名和密碼。 登錄成功時(shí),告訴 Spring Security 當(dāng)前用戶(hù)具備的權(quán)限。

所以我們開(kāi)發(fā)根據(jù)用戶(hù)名獲取用戶(hù)信息和根據(jù)用戶(hù)名獲取其可訪問(wèn)的 api 列表方法。

實(shí)例:

@Repository
public class UserDao {
	@Autowired
	private JdbcTemplate jdbcTemplate;
	/**
	 * 根據(jù)用戶(hù)名獲取用戶(hù)信息
	 */
	public List<UserDo> getUsersByUsername(String username) {
		String sql = "select id, username, password from user where username = ?";
		return jdbcTemplate.query(sql, new String[] { username }, new BeanPropertyRowMapper<>(UserDo.class));
	}
	/**
	 * 根據(jù)用戶(hù)名獲取其可訪問(wèn)的api列表
	 */
	public List<String> getApisByUsername(String username) {
		String sql = "select path from user left join roleapi on user.roleId=roleapi.roleId left join api on roleapi.apiId=api.id where username = ?";
		return jdbcTemplate.queryForList(sql, new String[] { username }, String.class);
	}
}

4.5 開(kāi)發(fā)服務(wù)類(lèi)

開(kāi)發(fā) SecurityService 類(lèi),保存安全管理相關(guān)的業(yè)務(wù)方法。

實(shí)例:

@Service
public class SecurityService {
	@Autowired
	private UserDao userDao;
	@Autowired
	private ApiDao apiDao;

	public List<UserDo> getUserByUsername(String username) {
		return userDao.getUsersByUsername(username);
	}

	public List<String> getApisByUsername(String username) {
		return userDao.getApisByUsername(username);
	}

	public List<String> getApiPaths() {
		return apiDao.getApiPaths();
	}
}

4.6 開(kāi)發(fā)控制器類(lèi)

開(kāi)發(fā)控制器類(lèi),其中 notLogin 方法是用戶(hù)未登錄時(shí)調(diào)用的方法,其他方法與權(quán)限表中的 api 一一對(duì)應(yīng)。

實(shí)例:

@RestController
public class TestController {
	/**
	 * 未登錄時(shí)調(diào)用該方法
	 */
	@RequestMapping("/notLogin")
	public ResultBo notLogin() {
		return new ResultBo(new Exception("未登錄"));
	}

	/**
	 * 查看商品
	 */
	@RequestMapping("/viewGoods")
	public ResultBo viewGoods() {
		return new ResultBo<>("viewGoods is ok");
	}

	/**
	 * 添加商品
	 */
	@RequestMapping("/addGoods")
	public ResultBo addGoods() {
		return new ResultBo<>("addGoods is ok");
	}
}

由于是前后端分離的項(xiàng)目,為了便于前端統(tǒng)一處理,我們封裝了返回?cái)?shù)據(jù)業(yè)務(wù)邏輯對(duì)象 ResultBo 。

實(shí)例:

public class ResultBo<T> {
	/**
	 * 錯(cuò)誤碼 0表示沒(méi)有錯(cuò)誤(異常) 其他數(shù)字代表具體錯(cuò)誤碼
	 */
	private int code;
	/**
	 * 后端返回消息
	 */
	private String msg;
	/**
	 * 后端返回的數(shù)據(jù)
	 */
	private T data;
	/**
	 * 無(wú)參數(shù)構(gòu)造函數(shù)
	 */
	public ResultBo() {
		this.code = 0;
		this.msg = "操作成功";
	}
	/**
	 * 帶數(shù)據(jù)data構(gòu)造函數(shù)
	 */
	public ResultBo(T data) {
		this();
		this.data = data;
	}
	/**
	 * 存在異常的構(gòu)造函數(shù)
	 */
	public ResultBo(Exception ex) {
		this.code = 99999;// 其他未定義異常
		this.msg = ex.getMessage();
	}
}

4.7 開(kāi)發(fā) Spring Security 配置類(lèi)

現(xiàn)在,我們就需要將用戶(hù)、權(quán)限等信息通過(guò)配置類(lèi)告知 Spring Security 了。

4.7.1 定義配置類(lèi)

定義 Spring Security 配置類(lèi),通過(guò)注解 @EnableWebSecurity 開(kāi)啟安全管理功能。

實(shí)例:

@Configuration
@EnableWebSecurity // 開(kāi)啟安全管理
public class SecurityConfig {
	@Autowired
	private SecurityService securityService;
}

4.7.2 注冊(cè)密碼加密組件

Spring Security 提供了很多種密碼加密組件,我們使用官方推薦的 BCryptPasswordEncoder ,直接注冊(cè)為 Bean 即可。

我們之前在數(shù)據(jù)庫(kù)中預(yù)定義的密碼字符串即為 123 加密后的結(jié)果。 Spring Security 在驗(yàn)證密碼時(shí),會(huì)自動(dòng)調(diào)用注冊(cè)的加密組件,將用戶(hù)輸入的密碼加密后再與數(shù)據(jù)庫(kù)密碼比對(duì)。

實(shí)例:

	@Bean
	PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
    public static void main(String[] args) {
		//輸出 $2a$10$kLQpA8S1z0KdgR3Cr6jJJ.R.QsIT7nrCdAfsF4Of84ZBX2lvgtbE.
		System.out.println(new BCryptPasswordEncoder().encode("123"));
	}

4.7.3 將用戶(hù)密碼及權(quán)限告知 Spring Security

通過(guò)注冊(cè) UserDetailsService 類(lèi)型的組件,組件中設(shè)置用戶(hù)密碼及權(quán)限信息即可。

實(shí)例:

	@Bean
	public UserDetailsService userDetailsService() {
		return username -> {
			List<UserDo> users = securityService.getUserByUsername(username);
			if (users == null || users.size() == 0) {
				throw new UsernameNotFoundException("用戶(hù)名錯(cuò)誤");
			}
			String password = users.get(0).getPassword();
			List<String> apis = securityService.getApisByUsername(username);
			// 將用戶(hù)名username、密碼password、對(duì)應(yīng)權(quán)限列表apis放入組件
			return User.withUsername(username).password(password).authorities(apis.toArray(new String[apis.size()]))
					.build();
		};
	}

4.7.4 設(shè)置訪問(wèn)路徑需要的權(quán)限信息

同樣,我們通過(guò)注冊(cè)組件,將訪問(wèn)路徑需要的權(quán)限信息告知 Spring Security 。

實(shí)例:

	@Bean
	public WebSecurityConfigurerAdapter webSecurityConfigurerAdapter() {
		return new WebSecurityConfigurerAdapter() {
			@Override
			public void configure(HttpSecurity httpSecurity) throws Exception {
				// 開(kāi)啟跨域支持
				httpSecurity.cors();
				// 從數(shù)據(jù)庫(kù)中獲取權(quán)限列表
				List<String> paths = securityService.getApiPaths();
				for (String path : paths) {
					/* 對(duì)/xxx/**路徑的訪問(wèn),需要具備xxx權(quán)限
					例如訪問(wèn) /addGoods,需要具備addGoods權(quán)限 */
					httpSecurity.authorizeRequests().antMatchers("/" + path + "/**").hasAuthority(path);
				}
				// 未登錄時(shí)自動(dòng)跳轉(zhuǎn)/notLogin
				httpSecurity.authorizeRequests().and().formLogin().loginPage("/notLogin")
						// 登錄處理路徑、用戶(hù)名、密碼
						.loginProcessingUrl("/login").usernameParameter("username").passwordParameter("password")
						.permitAll()
						// 登錄成功處理
						.successHandler(new AuthenticationSuccessHandler() {
							@Override
							public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
									HttpServletResponse httpServletResponse, Authentication authentication)
									throws IOException, ServletException {
								httpServletResponse.setContentType("application/json;charset=utf-8");
								ResultBo result = new ResultBo<>();
								ObjectMapper mapper = new ObjectMapper();
								PrintWriter out = httpServletResponse.getWriter();
								out.write(mapper.writeValueAsString(result));
								out.close();
							}
						})
						// 登錄失敗處理
						.failureHandler(new AuthenticationFailureHandler() {
							@Override
							public void onAuthenticationFailure(HttpServletRequest httpServletRequest,
									HttpServletResponse httpServletResponse, AuthenticationException e)
									throws IOException, ServletException {
								httpServletResponse.setContentType("application/json;charset=utf-8");
								ResultBo result = new ResultBo<>(new Exception("登錄失敗"));
								ObjectMapper mapper = new ObjectMapper();
								PrintWriter out = httpServletResponse.getWriter();
								out.write(mapper.writeValueAsString(result));
								out.flush();
								out.close();
							}
						});
				// 禁用csrf(跨站請(qǐng)求偽造)
				httpSecurity.authorizeRequests().and().csrf().disable();
			}
		};
	}

按上面的設(shè)計(jì),當(dāng)用戶(hù)發(fā)起訪問(wèn)時(shí):

  • 未登錄的訪問(wèn)會(huì)自動(dòng)跳轉(zhuǎn)到/notLogin 訪問(wèn)路徑。
  • 通過(guò) /login 訪問(wèn)路徑可以發(fā)起登錄請(qǐng)求,用戶(hù)名和密碼參數(shù)名分別為 username 和 password 。
  • 登錄成功或失敗會(huì)返回 ResultBo 序列化后的 JSON 字符串,包含登錄成功或失敗信息。
  • 訪問(wèn) /xxx 形式的路徑,需要具備 xxx 權(quán)限。用戶(hù)所具備的權(quán)限已經(jīng)通過(guò)上面的 UserDetailsService 組件告知 Spring Security 了。

5. 測(cè)試

啟動(dòng)項(xiàng)目后,我們使用 PostMan 進(jìn)行驗(yàn)證測(cè)試。

5.1 未登錄測(cè)試

在未登錄時(shí),直接訪問(wèn)控制器方法,會(huì)自動(dòng)跳轉(zhuǎn) /notLogin 訪問(wèn)路徑,返回未登錄提示信息。

圖片描述

未登錄測(cè)試

5.2 錯(cuò)誤登錄密碼測(cè)試

調(diào)用登錄接口,當(dāng)密碼不對(duì)時(shí),返回登錄失敗提示信息。

圖片描述

錯(cuò)誤登錄密碼測(cè)試

5.3 以 guest 用戶(hù)登錄

使用 guest 用戶(hù)及正確命名登錄,返回操作成功提示信息。

圖片描述

以 guest 用戶(hù)登錄

5.4 guest 用戶(hù)訪問(wèn)授權(quán)接口

按照數(shù)據(jù)庫(kù)中定義的規(guī)則, guest 用戶(hù)可以訪問(wèn) viewGoods 接口方法。

圖片描述

guest 用戶(hù)訪問(wèn)授權(quán)接口

5.5 guest 用戶(hù)訪問(wèn)未授權(quán)接口

按照數(shù)據(jù)庫(kù)中定義的規(guī)則, guest 沒(méi)有訪問(wèn) addGoods 接口方法的權(quán)限。

圖片描述

guest 用戶(hù)訪問(wèn)未授權(quán)接口

5.6 admin 用戶(hù)登錄及訪問(wèn)授權(quán)接口

按照數(shù)據(jù)庫(kù)中定義的規(guī)則, admin 用戶(hù)登錄后可以訪問(wèn) viewGoods 和 addGoods 兩個(gè)接口方法。

圖片描述

admin 用戶(hù)登錄

圖片描述

admin 用戶(hù)訪問(wèn)授權(quán)接口

圖片描述

admin 用戶(hù)訪問(wèn)授權(quán)接口

6. 小結(jié)

Spring Boot 整合 Spring Security ,實(shí)際上大部分工作都在安全管理配置類(lèi)上。

我們通過(guò)安全管理配置類(lèi),將用戶(hù)、密碼及其對(duì)應(yīng)的權(quán)限信息放入容器,同時(shí)將訪問(wèn)路徑所需要的權(quán)限信息放入容器, Spring Security 就會(huì)按照用戶(hù)訪問(wèn)路徑--判斷所需權(quán)限--用戶(hù)是否具備該權(quán)限--允許或拒絕訪問(wèn)這樣的邏輯實(shí)施權(quán)限管理了。