SpringBoot學習part4
文章目錄
-
- SpringBoot學習part4
-
- Shiro
-
- shiro的subject的分析
- spring boot整合shiro環境搭建
- shiro實作登入攔截
- shiro實作使用者認證
- shiro整合Mybatis
- shiro請求授權實作
- shiro整合Thymeleaf
- Swagger
- Dubbo
- Zookeeper
書接上回,繼續認識shiro
Shiro
shiro的subject的分析
分析QuickStar代碼
3大對象:
Subject——使用者
SecurityManager——管理所有使用者
Realm ——連接配接資料
//重點
// 1.
// get the currently executing user:
//擷取目前的使用者對象 Subject
Subject currentUser = SecurityUtils.getSubject();
// Do some stuff with a Session (no need for a web or EJB container!!!)
//通過目前使用者拿到Session
Session session = currentUser.getSession();
session.setAttribute("someKey", "aValue");
String value = (String) session.getAttribute("someKey");
if (value.equals("aValue")) {
log.info("Retrieved the correct value! [" + value + "]");
}
//2.
//判斷目前的使用者是否被認證
if (!currentUser.isAuthenticated()) {
//token:令牌
UsernamePasswordToken token = new UsernamePasswordToken("lonestarr", "vespa");
//設定記住我
token.setRememberMe(true);
try {
currentUser.login(token);//執行登入操作
} catch (UnknownAccountException uae) { //未知賬戶
log.info("There is no user with username of " + token.getPrincipal());
} catch (IncorrectCredentialsException ice) { //密碼不對
log.info("Password for account " + token.getPrincipal() + " was incorrect!");
} catch (LockedAccountException lae) { //使用者鎖定
log.info("The account for username " + token.getPrincipal() + " is locked. " +
"Please contact your administrator to unlock it.");
}
catch (AuthenticationException ae) { //認證異常
}
//3.
//test a role: 測試角色,hasRole在SpringSecurity中也存在
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
//4.
//粗粒度 test a typed permission (not instance-level)
//檢測權限
if (currentUser.isPermitted("lightsaber:wield")) {...}
//細粒度 a (very powerful) Instance Level permission
if (currentUser.isPermitted("winnebago:drive:eagle5")) {...}
//5.
//all done - 退出
currentUser.logout();
//結束啟動
System.exit(0);
spring boot整合shiro環境搭建
- 在之前的工程中建立module,選擇Spring Initializr,選SpringWeby依賴,建立,再添加個thymeleaf依賴
- 在templates中建立index.html
- 寫controller,給首頁傳遞個參數(hello,shiro),測試(成功)
- 導入shiro整合spring的包
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.1</version>
</dependency>
- 編寫配置類
//對應上面那三大類,配置的時候從下往上
//shiroFilterFactoryBean
//DefaultWebSecurityManager
//建立realm對象
shiroConfig
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean 步驟3
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//設定安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
return bean;
}
//DefaultWebSecurityManager 步驟2;
// 如何将UserRealm與步驟1中的UserRealm綁定,通過Spring傳遞,使用@Qualifier("userRealm")
@Bean(name="securityManager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//關聯realm
securityManager.setRealm(userRealm);
return securityManager;
}
//建立realm對象 ,需要自定義類(UserRealm) 步驟1
//@Bean(name="userRealm")
@Bean
public UserRealm userRealm(){
return new UserRealm();
}
}
- 在templates新增user/add.html和update.html,并在首頁添加跳轉連接配接
- 寫controller
@RequestMapping("/user/add")
public String add(){
return "user/add";
}
@RequestMapping("/user/update")
public String update(){
return "user/update";
}
- 啟動,shiro實驗環境搭建完成
shiro實作登入攔截
現在需要使得有的使用者可通路,有的使用者不能通路,進行實作
添加shiro的内置過濾器:
- anon:無需認證就可以通路;
- authc:必須認證了才能通路;
- user:必須有"記住我"功能才能使用;
- perms:擁有對某個資源的權限才能通路;
- role:有某個角色權限才能通路
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//設定安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//添加shiro的内置過濾器
// anon:無需認證就可以通路;
// authc:必須認證了才能通路;
// user:必須有"記住我"功能才能使用;
// perms:擁有對某個資源的權限才能通路;
// role:有某個角色權限才能通路
Map<String, String> filterMap = new LinkedHashMap<>();
filterMap.put("/user/add","anon"); //所有人都可通路add頁面
filterMap.put("/user/update","authc"); //需要認證才可通路,還需自己寫登陸頁面,然後跳轉
bean.setFilterChainDefinitionMap(filterMap);
//設定登入的請求,自己寫一個登陸頁面在templates下
bean.setLoginUrl("/tologin");
return bean;
}
controller中增加
@RequestMapping("/tologin")
public String tologin(){
return "login";
}
測試:點選Update就跳轉到登入,表明登入攔截成功!
shiro實作使用者認證
先來實作認證,在MyController類中進行實作:
//認證工作:
@RequestMapping("/login")
public String login(String username,String password,Model model){
//擷取目前使用者
Subject subject = SecurityUtils.getSubject();
//封裝使用者的登陸資料
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token); //執行登入方法,異常在quickstart例子中有說明
return "index";
}catch (UnknownAccountException e){ // 使用者名不存在
model.addAttribute("msg","使用者名錯誤");
return "login";
}catch (IncorrectCredentialsException e){ // 密碼異常
model.addAttribute("msg","密碼錯誤");
return "login";
}
}
在前端登入頁面接收msg
<h1>登入</h1>
<p th:text="${msg}" style="color: red;"></p>
<form th:action="@{/login}">
<p>使用者名:<input type="text" name="username"></p>
<p>密碼:<input type="password" name="password"></p>
<p><input type="submit"></p>
</form>
測試,點選Update跳至登入頁面,當輸入使用者名密碼錯誤:
且在IDEA列印:執行了UserRealm類中的認證方法
接着在UserRealm類的認證方法中添加:
//認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("執行了->認證doGetAuthenticationInfo");
//使用者名,密碼——資料庫中讀取
String name = "root";
String password = "123456";
//轉換token
UsernamePasswordToken userToken = (UsernamePasswordToken)token;
if (!userToken.getUsername().equals(name)){
return null; //就會抛出異常,即使用者名不存在異常
}
//密碼認證,shiro自己做
return new SimpleAuthenticationInfo("",password,"");
}
進行再次測試:當輸入root,123登入會顯示密碼錯誤;輸入正确的使用者名密碼後就可顯示首頁
shiro整合Mybatis
連接配接資料庫
- mysql驅動
<!--mysql驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
- 編寫配置檔案——application.yml
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&userUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
# springboot預設是不注入這些屬性值的,需要自己綁定
# druid資料源專有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMills: 60000
minEvictableIdleTimeMills: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnreturn: false
poolPrepareStatements: true
# 配置監控統計攔截的filters,stat:監控統計、log4j:日志記錄、wall:防禦sql注入
# 如果允許時報錯 java.lang.ClassNotFoundException: org.apache.log4j.Priority
# 則導入 log4j 依賴即可,Maven 位址: https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
- 使用idea連接配接上資料庫
- 在application.properties中綁定Mybatis
mybatis.type-aliases-package=com.feng.pojo
mybatis.mapper-locations=classpath:mapper/*.xml
- 搭環境
- 測試
@SpringBootTest
class ShiroSpringbootApplicationTests {
@Autowired
UserServiceImpl userService;
@Test
void contextLoads() {
System.out.println(userService.queryUserByName("天天"));
}
}
結果:輸出User(id=5, name=天天, pwd=111)
将之前寫的使用者名密碼現在變為從資料庫中讀取
//自定義的UserRealm 須繼承 AuthorizingRealm 即可
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("執行了->授權doGetAuthorizationInfo");
return null;
}
//認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("執行了->認證doGetAuthenticationInfo");
//轉換token
UsernamePasswordToken userToken = (UsernamePasswordToken)token;
//連接配接真實資料庫
User user = userService.queryUserByName(userToken.getUsername());
if (user==null){
return null;//就會抛出異常,即使用者名不存在異常
}
//密碼認證,shiro自己做
return new SimpleAuthenticationInfo("",user.getPwd(),"");
}
}
啟動後測試,輸入資料庫中的使用者名和密碼可進行登入
shiro請求授權實作
在ShiroFilterFactoryBean 中實作
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager")DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
//設定安全管理器
bean.setSecurityManager(defaultWebSecurityManager);
//攔截
Map<String, String> filterMap = new LinkedHashMap<>();
//授權,正常情況下,沒有授權會跳轉到未授權頁面
filterMap.put("/user/add","perms[user:add]"); //需要user有add權限才能通路
filterMap.put("/user/*","authc");
bean.setFilterChainDefinitionMap(filterMap);
//設定登入的請求
bean.setLoginUrl("/tologin");
//設定未授權的請求
bean.setUnauthorizedUrl("/noauth");
return bean;
}
在controller中控制跳轉的未授權頁面
//未授權頁面
@RequestMapping("/noauth")
@ResponseBody
public String unauthorized(){
return "未經授權無法通路此頁面";
}
測試:
先登入:
在首頁再通路add頁面:
且控制台列印:執行了->授權doGetAuthorizationInfo,說明我們可以在這裡面做些操作~
在沒寫未經通路頁面時,點選add會報401錯誤,類型是未授權
繼續完善程式:
在UserRealm,授權中寫:
//授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("執行了->授權doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//經過這裡就可以給使用者授權
info.addStringPermission("user:add");
return info;
}
啟動程式測試一下:現在無論什麼使用者登入之後,都可以進行add通路,因為都走了doGetAuthorizationInfo這個方法,給寫死了
是以,這個權限應該在資料庫中,于是修改資料庫,添加字段
- 改pojo
- 使用者登入從認證中查出perms
在認證中的使用者,如何拿到授權當中呢?
//認證
....
return new SimpleAuthenticationInfo(user,user.getPwd(),"")
//這第一個參數屬性是principal,傳進user
//在授權方法中,如何拿到?
//拿到目前登入的對象
Subject subject = SecurityUtils.getSubject();
User currentUser = (User) subject.getPrincipal();
整體授權方法:
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("執行了->授權doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//經過這裡就可以給使用者授權
// info.addStringPermission("user:add");
//拿到目前登入的對象
Subject subject = SecurityUtils.getSubject();
//拿到User對象
User currentUser = (User) subject.getPrincipal();
//設定目前使用者權限
info.addStringPermission(currentUser.getPerms());
return info;
}
//認證
....
}
shiroConfig配置,加上update的:
//授權,正常情況下,沒有授權會跳轉到未授權頁面
filterMap.put("/user/add","perms[user:add]"); //需要user有add權限才能通路
filterMap.put("/user/update","perms[user:update]");
啟動測試:現在登入root使用者,隻能通路update頁面,無權限通路add,登入小宋,隻能通路add~
shiro整合Thymeleaf
還有一個問題,登入了使用者之後,首頁應該隻顯示這個使用者能通路的頁面,而其他頁面不可見
所需:
- shiro和thymeleaf整合包的導入
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
- 需要在shiroConfig中進行配置才可使用
//整合shiroDialect:用來整合shiro thymeleaf
@Bean
public ShiroDialect getShiroDialect(){
return new ShiroDialect();
}
- 在前端測試,index.html中的标簽按照使用者權限來顯示
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首頁</h1>
<p>
<a th:href="@{/tologin}">登入</a>
</p>
<p th:text="${msg}"></p>
<hr>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">Add</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">Update</a>
</div>
</body>
</html>
測試:
此時,登入root使用者,就隻會顯示隻能它通路的頁面
登入成功之後,去除登入按鈕:
在UserRealm中
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
//授權
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("執行了->授權doGetAuthorizationInfo");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//經過這裡就可以給使用者授權
// info.addStringPermission("user:add");
//拿到目前登入的對象
Subject subject = SecurityUtils.getSubject();
//拿到User對象
User currentUser = (User) subject.getPrincipal();
//設定目前使用者權限
info.addStringPermission(currentUser.getPerms());
return info;
}
//認證
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
System.out.println("執行了->認證doGetAuthenticationInfo");
//轉換token
UsernamePasswordToken userToken = (UsernamePasswordToken)token;
//連接配接真實資料庫
User user = userService.queryUserByName(userToken.getUsername());
if (user==null){
return null;//就會抛出異常,即使用者名不存在異常
}
//拿到此刻的session,并寫屬性,讓前端首頁做判斷
Subject currentsubject = SecurityUtils.getSubject();
Session session = currentsubject.getSession();
session.setAttribute("loginUser",user);
return new SimpleAuthenticationInfo(user,user.getPwd(),"");
}
}
index.html頁面:使得使用者第一次通路首頁出現登入按鈕,不出現登出按鈕,登陸後,在頁面上顯示登出按鈕,不顯示登入按鈕
<body>
<h1>首頁</h1>
<div th:if="${session.loginUser==null}">
<a th:href="@{/tologin}">登入</a>
</div>
<div th:if="${!(session.loginUser==null)}">
<a th:href="@{/logout}">登出</a>
</div>
<p th:text="${msg}"></p>
<hr>
<div shiro:hasPermission="user:add">
<a th:href="@{/user/add}">Add</a>
</div>
<div shiro:hasPermission="user:update">
<a th:href="@{/user/update}">Update</a>
</div>
</body>
在controller中把登出功能寫:
//登出
@RequestMapping("/logout")
public String logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
return "login";
}
進行通路測試:
第一次通路:
登入root使用者:
進入屬于它的頁面,以及登出按鈕:
點選登出:會跳轉至登入頁面!
到這裡入門執行個體就結束了
Swagger
Dubbo
Zookeeper
後續跟進,繼續更新此處筆記