1. 程式人生 > 實用技巧 >簡單的shiro應用(學習狂神說)

簡單的shiro應用(學習狂神說)

一、上程式碼

controller

package com.wxl.controller;

@Controller
public class IndexController {
    @RequestMapping({"/","index"})
    public String toIndex(Model model){
        model.addAttribute("welcome","歡迎!");
        return "index";
    }
    
    @RequestMapping("/toLogin")
    public String toLogin(){
        return "login";
    }  //templates下面的login.html

    @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); //執行登入方法,如果沒有異常就說明ok了
            return "index";
        } catch (UnknownAccountException e){ //使用者名稱不存在
            model.addAttribute("msg", "使用者名稱錯誤");
            return "login";
        } catch (IncorrectCredentialsException e){ //密碼不存在
            model.addAttribute("msg","密碼錯誤");
            return "login";
        } catch (AuthenticationException e) {
            e.printStackTrace();
            return "login";
        }
    }

    @RequestMapping("/unauth")
    @ResponseBody
    public String unauth(){
        return "您沒有許可權訪問該頁面!";
    }

    @RequestMapping("/user/add")
    public String toAdd(){
        return "user/add";
    }

    @RequestMapping("/user/update")
    public String toUpdate(){
        return "user/update";
    }
}

UserRealm

package com.wxl.config;

//自定義UserRealm extends AuthorizingRealm
public class UserRealm extends AuthorizingRealm {

    @Autowired
    UserService userService;

    //授權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        System.out.println("執行了=>授權doGetAuthorizationInfo...");
        //SimpleAuthorizationInfo
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //拿到當前登入的物件
        Subject subject = SecurityUtils.getSubject();
        User principal = (User) subject.getPrincipal();
        //設定當前使用者的許可權,許可權從資料庫中讀取
        info.addStringPermission(principal.getPerms());

        return info;
    }

    //認證
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        System.out.println("執行了=>認證doGetAuthenticationInfo...");

        //使用者名稱,密碼~     資料庫中取
        UsernamePasswordToken usertoken = (UsernamePasswordToken) token;
        User user = userService.QueryUserByName(usertoken.getUsername());
        if (user==null){
            return null;   //丟擲異常UnknownAccountException
        }

        /*把登入成功的資訊存到session裡面,用於頁面的判斷*/
        Subject current = SecurityUtils.getSubject();
        Session session = current.getSession();
        session.setAttribute("loginUser",user);

        //密碼認證  shiro做~
        return new SimpleAuthenticationInfo(user,user.getPwd(),"");
    }
}

ShiroConfig

package com.wxl.config;

@Configuration
public class ShiroConfig {
    //ShiroFilterFactoryBean   第三步
    //DefaultWebSecurityManager  第二步
    //建立realm物件  需要自定義    第一步

    @Bean
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        //設定安全管理器
        filterFactoryBean.setSecurityManager(securityManager);

        //新增shiro的內建過濾器
        /*  過濾規則 :
            anon : 無需認證就可以訪問
            authc : 必須認證才能訪問
            user : 必須擁有 記住我 才能使用
            perms : 擁有對某個資源的許可權才能訪問
            role : 擁有某個角色許可權才能訪問
         */
        /*filterMap.put("/toLogin","anon");
        filterMap.put("/user/add","authc");
        filterMap.put("/user/update","authc");*/
        //攔截
        Map<String, String> filterMap = new LinkedHashMap<>();

        //filterMap中要先授權再攔截

        //授權
        filterMap.put("/user/add","perms[user:add]");
        filterMap.put("/user/update","perms[user:update]");
        //認證才能訪問add和update頁面
        filterMap.put("/user/*","authc");  //萬用字元  user下面的所有路徑
        filterFactoryBean.setFilterChainDefinitionMap(filterMap); //設定鏈過濾

        //設定登入的請求
        filterFactoryBean.setLoginUrl("/toLogin");
        //設定未授權的請求
        filterFactoryBean.setUnauthorizedUrl("/unauth");

        return filterFactoryBean;
    }

    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //關聯UserRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }


    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }

    /*這裡做shiro和thymeleaf整合*/
    @Bean
    public ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }
}

Dao

User QueryUserByName(String name);

User

package com.wxl.pojo;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
    private String perms;
}

頁面

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>
<div th:text="${welcome}"></div>

<!--把登入資訊存到session裡面,session為空顯示登入,否則不顯示-->
<div th:if="${session.loginUser==null}">
    <a th:href="@{/toLogin}">登入</a>
</div>

<div shiro:hasPermission="user:add">  <!--使用者擁有user:add這個許可權就顯示-->
    <a th:href="@{/user/add}">新增</a>
</div>
<div shiro:hasPermission="user:update">
    <a th:href="@{/user/update}">更新</a>
</div>

</body>
</html>

login.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登入頁面</title>
</head>
<body>
<div style="width:100%;">
    <h3 style="width:100%;text-align: center">登入</h3>
</div>

<div style="width:100%;text-align: center">
    <p th:text="${msg}" style="color: red"></p>
    <form action="/login" >
        <p>使用者名稱:<input type="text" name="username"></p>
        <p>密&nbsp;&nbsp;&nbsp;碼:<input type="password" name="password"></p>
        <p><input type="submit"></p>
    </form>
</div>
</body>
</html>

二、準備工作

  1. pom.xml
<dependencies>

    <!--shiro-->
    <!--
        subject : 使用者
        sercurityManager : 管理所有使用者
        realm : 連線資料
    -->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring-boot-starter</artifactId>
        <version>1.4.1</version>
    </dependency>

    <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>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>

    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>

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

    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.16</version>
    </dependency>

    <!--thymeleaf和shiro整合包-->
    <dependency>
        <groupId>com.github.theborakompanioni</groupId>
        <artifactId>thymeleaf-extras-shiro</artifactId>
        <version>2.0.0</version>
    </dependency>
</dependencies>
  1. application.yml
mybatis:
  mapper-locations: classpath:mapper/*.xml
  config-location:
    map-underscore-to-camel-case: true  #開啟mybatis的駝峰命名
  type-aliases-package: com.wxl.pojo
  
spring:
  datasource:
    druid:
      username: root
      password: root
      #?serverTimezone=UTC解決時區的報錯
      url: jdbc:mysql://localhost:3306/demo_datebase?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
      driver-class-name: com.mysql.jdbc.Driver
      type: com.alibaba.druid.pool.DruidDataSource

      #Spring Boot 預設是不注入這些屬性值的,需要自己繫結
      #druid 資料來源專有配置
      initial-size: 5
      min-idle: 5
      max-active: 20
      max-wait: 60000
      time-between-eviction-runs-millis: 60000  #有兩個含義: 1) Destroy執行緒會檢測連線的間隔時間2) testWhileIdle的判斷依據,詳細看testWhileIdle屬性的說明
      min-evictable-idle-time-millis: 300000
      validation-query: select 1 from dual  #用來檢測連線是否有效的sql,要求是一個查詢語句。如果validationQuery為null,testOnBorrow、testOnReturn、testWhileIdle都不會其作用
      test-on-return: false  #申請連線時執行validationQuery檢測連線是否有效,做了這個配置會降低效能。
      test-on-borrow: false  #歸還連線時執行validationQuery檢測連線是否有效,做了這個配置會降低效能
      test-while-idle: true  #建議配置為true,不影響效能,並且保證安全性。申請連線的時候檢測,如果空閒時間大於timeBetweenEvictionRunsMillis,執行validationQuery檢測連線是否有效。
      pool-prepared-statements: true #是否快取preparedStatement,也就是PSCache。PSCache對支援遊標的資料庫效能提升巨大,比如說oracle。在mysql下建議關閉。
      max-pool-prepared-statement-per-connection-size: 20  #指定每個連線上PSCache的大小

      #配置監控統計攔截的filters, stat: 監控統計,wall: 防禦sql注入,log4j: 日誌記錄
      #如果允許時報錯 java.lang.ClassNotFoundException: org.apache.log4j.Priority
      #則匯入log4j 依賴即可
      filters: stat,wall,log4j  #屬性型別是字串,通過別名的方式配置擴充套件外掛,常用的外掛有: 監控統計用的filter:stat  日誌用的filter:log4j  防禦sql注入的filter:wall
      use-global-data-source-stat: true
      connect-properties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500