天天看點

Day46——SpringBoot學習筆記part4

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環境搭建

  1. 在之前的工程中建立module,選擇Spring Initializr,選SpringWeby依賴,建立,再添加個thymeleaf依賴
  2. 在templates中建立index.html
  3. 寫controller,給首頁傳遞個參數(hello,shiro),測試(成功)
  4. 導入shiro整合spring的包
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.1</version>
</dependency>
           
  1. 編寫配置類
//對應上面那三大類,配置的時候從下往上
//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();
    }

}
           
  1. 在templates新增user/add.html和update.html,并在首頁添加跳轉連接配接
  2. 寫controller
@RequestMapping("/user/add")
public String add(){
    return "user/add";
}
@RequestMapping("/user/update")
public String update(){
    return "user/update";
}
           
  1. 啟動,shiro實驗環境搭建完成
Day46——SpringBoot學習筆記part4

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跳至登入頁面,當輸入使用者名密碼錯誤:

Day46——SpringBoot學習筆記part4

且在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登入會顯示密碼錯誤;輸入正确的使用者名密碼後就可顯示首頁

Day46——SpringBoot學習筆記part4

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連接配接上資料庫
Day46——SpringBoot學習筆記part4
  • 在application.properties中綁定Mybatis
mybatis.type-aliases-package=com.feng.pojo
mybatis.mapper-locations=classpath:mapper/*.xml
           
  • 搭環境
Day46——SpringBoot學習筆記part4
  • 測試
@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 "未經授權無法通路此頁面";
}
           

測試:

先登入:

Day46——SpringBoot學習筆記part4

在首頁再通路add頁面:

Day46——SpringBoot學習筆記part4

且控制台列印:執行了->授權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這個方法,給寫死了

是以,這個權限應該在資料庫中,于是修改資料庫,添加字段

Day46——SpringBoot學習筆記part4
  • 改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>
           

測試:

Day46——SpringBoot學習筆記part4

此時,登入root使用者,就隻會顯示隻能它通路的頁面

Day46——SpringBoot學習筆記part4

登入成功之後,去除登入按鈕:

在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";
    }
           

進行通路測試:

第一次通路:

Day46——SpringBoot學習筆記part4

登入root使用者:

Day46——SpringBoot學習筆記part4

進入屬于它的頁面,以及登出按鈕:

Day46——SpringBoot學習筆記part4

點選登出:會跳轉至登入頁面!

到這裡入門執行個體就結束了

Swagger

Dubbo

Zookeeper

後續跟進,繼續更新此處筆記