在SpringBoot2中使用Apache Shiro實現(xiàn)登錄身份驗證和訪問權限控制

作為一個Apache Shiro小白壤玫,最近跟著“純潔的微笑”和“冷豪”等的博客豁护,學習了一下Apache Shiro。將自己的一些簡單理解記錄下來欲间,希望對你有所幫助楚里。

Apache Shiro在我工作項目中主要用于登陸身份驗證訪問權限控制。工作項目用的是SpringMVC框架猎贴,最近學習SpringBoot2腻豌,我就在SpringBoot2中來驗證一下Apache Shiro。以下Apache Shiro簡稱Shiro嘱能。

一、Shiro登陸架構

下面是Shiro的用戶登陸架構圖虱疏,我們根據(jù)箭頭來看一下流程惹骂。

image.png

1、Token:使用用戶的登錄信息創(chuàng)建令牌

UsernamePasswordToken token = new UsernamePasswordToken(username, password, true);

我們要先通過用戶名和密碼做瞪,生成一個token对粪,token是一個用戶令牌右冻,用于在登陸的時候,Shiro來驗證用戶是否有合法的身份著拭。

2纱扭、Subject:執(zhí)行登陸動作(login)

Subject subject = SecurityUtils.getSubject(); // 獲取Subject單例對象
subject.login(token); // 登陸

再通過Subject來執(zhí)行登陸操作,將token發(fā)送給Security Manager儡遮,讓他來驗證這個token乳蛾。Subject中文翻譯是主題。你可以理解為它是一個用戶鄙币,是User的抽象概念肃叶。

3、Realm:自定義代碼實現(xiàn)登陸身份驗證和訪問權限控制

先來看看Realm十嘿,你從上圖可以看出因惭,Realm在Shiro方框的外面。圖片很形象绩衷,因為這一部分恰恰是需要我們自己去實現(xiàn)的蹦魔。需要我們來設計如何驗證登錄用戶的身份(role),和這個用戶是否具有訪問某個URL的權限(permission)咳燕。前者使用AuthenticationInfo(驗證)實現(xiàn)勿决,后者使用AuthorizationInfo(授權)實現(xiàn)。

4迟郎、Security Manager:Shiro架構的核心

Security Manager剥险,是Shiro架構的核心,簡單來說宪肖,它根據(jù)我們自定義的Realm表制,去完成驗證和授權工作


如果這部分沒有看懂,建議先根據(jù)下面的“與SpringBoot2集成”部分控乾,搭建一個demo么介,在項目中直觀體驗一下了,再回來看蜕衡。

二壤短、與SpringBoot2集成

注:以下內(nèi)容是根據(jù)“純潔的微笑”大神的《springboot整合shiro-登錄認證和權限管理》一文,將其中SpringBoot1.5升級到2.1慨仿,針對2.1做了相應修改久脯,同時針對一些知識點延伸學習。博文地址:http://www.ityouknow.com/springboot/2017/06/26/springboot-shiro.html

建議你手動搭建一個demo镰吆,這樣能更深入了解帘撰。

pom依賴

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <!--web核心-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--thymeleaf模板-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!--HTML掃描器和標簽補償器,補充thymeleaf對html的嚴格檢驗-->
        <dependency>
            <groupId>net.sourceforge.nekohtml</groupId>
            <artifactId>nekohtml</artifactId>
            <version>1.9.22</version>
        </dependency>
        <!--Apache Shiro-->
        <dependency>
            <groupId>org.apache.shiro</groupId>
            <artifactId>shiro-spring</artifactId>
            <version>1.4.0</version>
        </dependency>
        <!--使用Spring Data JPA和Hibernate-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <!--用于MySQL的JDBC Type 4驅(qū)動程序-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--熱部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <!--可使用注解自動生成getter万皿、setter等方法-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
        </dependency>
</dependencies>

<!--為使用熱部署摧找,配置<build></build>-->
<build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <fork>true</fork>
                </configuration>
            </plugin>
        </plugins>
</build>

配置文件

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/springboot_shiro?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: 12345678
    driver-class-name: com.mysql.cj.jdbc.Driver

  thymeleaf:
    cache: false #禁用模板引擎編譯的緩存結果核行。由熱部署來實現(xiàn),更改代碼后蹬耘,使用Ctrl+F9(IDEA)更新
    mode: LEGACYHTML5 #避免thymeleaf對html文件的嚴格校驗(如檢查標簽必須對稱等)
  
  #使用jpa技術芝雪,運行實體代碼自動生成數(shù)據(jù)表
  jpa:
    database: mysql
    show-sql: true
    hibernate:
      ddl-auto: update
    properties:
        hibernate:
          dialect: org.hibernate.dialect.MySQL5Dialect

server:
  port: 9090

數(shù)據(jù)庫設計

使用基于角色的訪問控制(Role-Based Access Control)---RBAC 來實現(xiàn)數(shù)據(jù)庫設計,用戶依賴角色综苔,角色依賴權限惩系。這樣設計結構清晰,管理方便休里。建立三張表:user_info蛆挫,sys_role,sys_permission妙黍。使用sys_user_role關聯(lián)用戶和角色悴侵,使用sys_role_permission關聯(lián)角色和權限,不使用外鍵拭嫁。

使用jpa技術可免,運行實體代碼自動生成數(shù)據(jù)表。

用戶信息實體做粤。@Getter浇借、@Setter注解用于提供讀寫屬性。因為有getCredentialsSalt()怕品,所以不使用@Data注解妇垢。

@Entity
@Getter
@Setter
public class UserInfo implements Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)//GenerationType.IDENTITY避免生成hibernate_sequence表
    private Integer uid;
    @Column(unique = true)
    private String username;//帳號
    private String name;//名稱(昵稱或者真實姓名,不同系統(tǒng)不同定義)
    private String password; //密碼;
    private String salt;//加密密碼的鹽
    private byte state;//用戶狀態(tài),0:創(chuàng)建未認證(比如沒有激活肉康,沒有輸入驗證碼等等)--等待驗證的用戶 , 1:正常狀態(tài),2:用戶被鎖定.

    @ManyToMany(fetch = FetchType.EAGER)//立即從數(shù)據(jù)庫中進行加載數(shù)據(jù);
    @JoinTable(name = "SysUserRole", joinColumns = {@JoinColumn(name = "uid")}, inverseJoinColumns = {@JoinColumn(name = "roleId")})
    private List<SysRole> roleList;// 一個用戶具有多個角色

    /**
     * 密碼鹽闯估,重新對鹽重新進行了定義,用戶名+salt吼和,這樣就更加不容易被破解
     *
     * @return
     */
    public String getCredentialsSalt() {
        return this.username + this.salt;
    }
}

角色實體涨薪。使用@Data注解,為類提供讀寫屬性, 此外還提供了 equals()炫乓、hashCode()刚夺、toString() 方法。上面用戶信息實體和角色實體會根據(jù)@JoinTable注解生成sys_user_role表末捣。

@Entity
@Data
public class SysRole {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id; // 編號
    private String role; // 角色標識程序中判斷使用,如"admin",這個是唯一的:
    private String description; // 角色描述,UI界面顯示使用
    private Boolean available = Boolean.FALSE; // 是否可用,如果不可用將不會添加給用戶

    // 用戶 - 角色關系定義;
    @ManyToMany
    @JoinTable(name = "SysUserRole", joinColumns = {@JoinColumn(name = "roleId")}, inverseJoinColumns = {@JoinColumn(name = "uid")})
    private List<UserInfo> userInfos;// 一個角色對應多個用戶
    
    //角色 -- 權限關系:多對多關系;
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "SysRolePermission", joinColumns = {@JoinColumn(name = "roleId")}, inverseJoinColumns = {@JoinColumn(name = "permissionId")})
    private List<SysPermission> permissions;
}

權限實體侠姑。同理,角色實體和權限實體箩做,通過@JoinTable注解生成sys_role_permission表

@Entity
@Data
public class SysPermission {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;//主鍵.
    private String name;//名稱.
    @Column(columnDefinition = "enum('menu','button')")
    private String resourceType;//資源類型莽红,[menu|button]
    private String url;//資源路徑.
    private String permission; //權限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view
    private Long parentId; //父編號
    private String parentIds; //父編號列表
    private Boolean available = Boolean.FALSE;

    @ManyToMany
    @JoinTable(name = "SysRolePermission", joinColumns = {@JoinColumn(name = "permissionId")}, inverseJoinColumns = {@JoinColumn(name = "roleId")})
    private List<SysRole> roles;
}

數(shù)據(jù)庫數(shù)據(jù):

INSERT INTO `user_info` (`uid`,`username`,`name`,`password`,`salt`,`state`) VALUES ('1', 'admin', '管理員', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 0);
INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (1,0,'用戶管理',0,'0/','userInfo:view','menu','userInfo/userList');
INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (2,0,'用戶添加',1,'0/1','userInfo:add','button','userInfo/userAdd');
INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (3,0,'用戶刪除',1,'0/1','userInfo:del','button','userInfo/userDel');
INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (1,0,'管理員','admin');
INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (2,0,'VIP會員','vip');
INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (3,1,'test','test');
INSERT INTO `sys_role_permission` VALUES ('1', '1');
INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (2,1);
INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (3,2);
INSERT INTO `sys_user_role` (`role_id`,`uid`) VALUES (1,1);

三卒茬、配置Shiro

Apache Shiro 核心通過 Filter 來實現(xiàn)船老,就好像SpringMvc 通過DispachServletqu去實現(xiàn)。

Filter和Interceptor的區(qū)別:

Filter是過濾器圃酵,Interceptor是攔截器柳畔。前者基于回調(diào)函數(shù)實現(xiàn),必須依靠容器支持郭赐。因為需要容器裝配好整條FilterChain并逐個調(diào)用薪韩。后者基于代理實現(xiàn),屬于AOP的范疇捌锭。

在Shrio中實現(xiàn)登陸身份驗證和訪問權限控制有三種方式:

  • 1俘陷、完全使用注解來實現(xiàn)登陸身份驗證和訪問權限控制
  • 2、完全使用URL配置來實現(xiàn)登陸身份驗證和訪問權限控制
  • 3观谦、使用URL配置來實現(xiàn)登陸身份驗證拉盾、使用注解來實現(xiàn)訪問權限控制

第3種方式最靈活,所以用第三種豁状。

1捉偏、使用URL配置來實現(xiàn)登陸身份驗證

要實現(xiàn)當用戶在瀏覽器地址訪問項目URL時,Shiro會攔截所有的請求泻红,再根據(jù)配置的ShrioFilter過濾器來進行下一步操作夭禽。原理:Spring容器會將所有的Filter交給ShiroFilter管理。

@Configuration
public class ShiroConfig {
   
    @Bean
    public ShiroFilterFactoryBean shiroFilter() {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager());
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 以下過濾器按順序判斷
        // 配置不會被攔截的鏈接谊路,一般是排除前端文件(anon:指定的url可以匿名訪問)
//        filterChainDefinitionMap.put("/static/**", "anon");
        //配置退出 過濾器,其中的具體的退出代碼Shiro已經(jīng)替我們實現(xiàn)了
        filterChainDefinitionMap.put("/logout", "logout");
        //authc:所有url都必須認證通過才可以訪問;
        filterChainDefinitionMap.put("/**", "authc");

        //當項目訪問其他沒有通過認證的URL時讹躯,會默認跳轉(zhuǎn)到/login,如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面
        shiroFilterFactoryBean.setLoginUrl("/login");
        //登錄成功后要跳轉(zhuǎn)的鏈接
        shiroFilterFactoryBean.setSuccessUrl("/index");
        //當用戶訪問沒有權限的URL時缠劝,跳轉(zhuǎn)到未授權界面
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
    
    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(myShiroRealm());
        return securityManager;
    }
    
    @Bean
    public MyShiroRealm myShiroRealm() {
        MyShiroRealm myShiroRealm = new MyShiroRealm();
        myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
    }
}

authc更深層次含義:指定url需要form表單登錄潮梯,默認會從請求中獲取username、password等參數(shù)并嘗試登錄剩彬,如果登錄不了就會跳轉(zhuǎn)到loginUrl配置的路徑

在Realm中實現(xiàn)AuthenticationInfo(登陸身份驗證)

doGetAuthenticationInfo():用于驗證token的User是否具有合法的身份酷麦,即檢驗賬號密碼是否正確,每次用戶登錄的時候都會調(diào)用喉恋。

public class MyShiroRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //獲取登錄的用戶名
        String username = (String) authenticationToken.getPrincipal();
        //根據(jù)用戶名在數(shù)據(jù)庫中查找此用戶
        //實際項目中沃饶,這里可以根據(jù)實際情況做緩存,如果不做轻黑,Shiro自己也是有時間間隔機制糊肤,2分鐘內(nèi)不會重復執(zhí)行該方法
        UserInfo userInfo = userService.findByUsername(username);
        if (userInfo == null) {
            return null;
        }
        //根據(jù)salt來驗證token中的密碼是否跟從數(shù)據(jù)庫查找的密碼匹配,匹配則登錄成功氓鄙。getName()設置當前Realm的唯一名稱馆揉,可自定義
        return new SimpleAuthenticationInfo(
                userInfo,
                userInfo.getPassword(),
                ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//鹽
                getName());
    }
}

驗證密碼原理

先了解兩個算法献雅,散列算法與加密算法邪媳。

兩者都是將一個Object變成一串無意義的字符串久又,不同點是經(jīng)過散列的對象無法復原煞茫,是一個單向的過程。例如噩茄,對密碼的加密通常就是使用散列算法下面,因此用戶如果忘記密碼只能通過修改而無法獲取原始密碼。但是對于信息的加密則是正規(guī)的加密算法绩聘,經(jīng)過加密的信息是可以通過秘鑰解密和還原沥割。

在這里,我們將用戶的密碼使用散列算法(MD5)加密后保存到數(shù)據(jù)庫凿菩。加密的時候就使用了salt机杜,salt中文翻譯是鹽,你可以將他看成一個鑰匙衅谷。

因為散列算法加密是單項的椒拗,不能還原。那我們?nèi)绾蝸眚炞C密碼呢会喝,這時候也需要使用salt陡叠。我們將token中的明文密碼,采用生成密文密碼時一樣的方式肢执,通過salt再加密一次枉阵,對比兩個加密后的密碼。最后的驗證是SimpleAuthenticationInfo去實現(xiàn)的预茄。

如何散列加密

//newPassword(密文密碼):d3c59d25033dbf980d29554025c23a75
String newPassword = new SimpleHash("MD5",//散列算法:這里使用MD5算法
        "123456",//明文密碼
        ByteSource.Util.bytes("admin8d78869f470951332959580424d4bf4f"),//salt:用戶名 + salt
        2//散列的次數(shù)兴溜,相當于MD5(MD5(**))
).toHex();

//生成一個32位數(shù)的salt
byte[] saltByte = new byte[16];
SecureRandom random = new SecureRandom();
random.nextBytes(saltByte);
String salt = Hex.encodeToString((saltByte));   

如何驗證密碼

配置hashedCredentialsMatcher(憑證匹配器),讓SimpleAuthorizationInfo知道如何驗證密碼:

@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
    HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
    hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法
    hashedCredentialsMatcher.setHashIterations(2);//散列的次數(shù)
    return hashedCredentialsMatcher;
}

2耻陕、使用注解來實現(xiàn)訪問權限控制

@Controller
@RequestMapping("/userInfo")
public class UserInfoController {
    /**
     * 用戶查詢;
     * @return
     */
    @RequestMapping("/userList")
    @RequiresPermissions("userInfo:view")//訪問的權限
    public String userList(){
        return "userInfo";
    }

    /**
     * 用戶添加;
     * @return
     */
    @RequestMapping("/userAdd")
    @RequiresPermissions("userInfo:add")//新增的權限
    public String userAdd(){
        return "userInfoAdd";
    }

    /**
     * 用戶刪除;
     * @return
     */
    @RequestMapping("/userDel")
    @RequiresPermissions("userInfo:del")//刪除的權限
    public String userDel(){
        return "userInfoDel";
    }
}

在Relm中實現(xiàn)AuthorizationInfo(訪問權限控制)

如果項目只需要Apache Shiro用于登陸驗證拙徽,那么就不用使用AuthorizationInfo,只需要返回一個null诗宣。

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
    return null;
}

doGetAuthorizationInfo():當用戶訪問帶有@RequiresPermissions注解的URL時膘怕,會調(diào)用此方法驗證是否有權限訪問。

protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("權限配置-->MyShiroRealm.doGetAuthorizationInfo()");
        UserInfo userInfo = (UserInfo) principalCollection.getPrimaryPrincipal();
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
        //獲取當前用戶的角色與權限召庞,讓simpleAuthorizationInfo去驗證
        for (SysRole sysRole : userInfo.getRoleList()) {
            simpleAuthorizationInfo.addRole(sysRole.getRole());
            for (SysPermission sysPermission : sysRole.getPermissions()) {
                simpleAuthorizationInfo.addStringPermission(sysPermission.getPermission());
            }
        }
        return simpleAuthorizationInfo;
}

代碼開啟注解

使用Shrio注解岛心,需要在ShrioConfig中使用AuthorizationAttributeSourceAdvisor開啟

@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
}

自定義異常處理

當沒有訪問權限時,會拋出異常篮灼,需要自定義異常處理忘古,將沒有權限的異常重定向到403頁面

@Bean
    public SimpleMappingExceptionResolver
    createSimpleMappingExceptionResolver() {
        System.out.println("自定義異常處理");
        SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
        Properties mappings = new Properties();
        mappings.setProperty("UnauthorizedException", "403");//授權異常處理
        resolver.setExceptionMappings(mappings);  // None by default
        resolver.setDefaultErrorView("error");    // No default
        resolver.setExceptionAttribute("ex");     // Default is "exception"
        return resolver;
    }

同理,可以使用@RequiresRoles("admin")注解來驗證角色(身份)

在前端頁面使用Shiro標簽時也會觸發(fā)權限控制诅诱,請看:http://www.reibang.com/p/6786ddf54582

其他兩種驗證和授權請看:https://juejin.im/entry/5ad95ef26fb9a07a9f01185a

也可直接編代碼測試:

Boolean isPermitted = SecurityUtils.getSubject().isPermitted("***");//是否有什么權限
Boolean hasRole = SecurityUtils.getSubject().hasRole("***");//是否有什么角色

登陸實現(xiàn)

前端登陸頁面:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
錯誤信息:<h4 th:text="${msg}"></h4>
<form action="" method="post">
    <p>賬號:<input type="text" name="username" value="admin"/></p>
    <p>密碼:<input type="text" name="password" value="123456"/></p>
    <p><input type="submit" value="登錄"/></p>
</form>
</body>
</html>

登陸接口:

@Controller
public class LoginController {

    @RequestMapping("/login")
    public String toLogin(HttpServletRequest request, Map<String, Object> map) {
        System.out.println("HomeController.login()");
        // 登錄失敗從request中獲取shiro處理的異常信息髓堪。
        // shiroLoginFailure:就是shiro異常類的全類名.
        String exception = (String) request.getAttribute("shiroLoginFailure");
        System.out.println("exception=" + exception);
        String msg = "";
        if (exception != null) {
            if (UnknownAccountException.class.getName().equals(exception)) {
                System.out.println("UnknownAccountException -- > 賬號不存在:");
                msg = "UnknownAccountException -- > 賬號不存在:";
            } else if (IncorrectCredentialsException.class.getName().equals(exception)) {
                System.out.println("IncorrectCredentialsException -- > 密碼不正確:");
                msg = "IncorrectCredentialsException -- > 密碼不正確:";
            } else if ("kaptchaValidateFailed".equals(exception)) {
                System.out.println("kaptchaValidateFailed -- > 驗證碼錯誤");
                msg = "kaptchaValidateFailed -- > 驗證碼錯誤";
            } else {
                msg = "else >> "+exception;
                System.out.println("else -- >" + exception);
            }
        }
        map.put("msg", msg);
        return "login";
    }

    @RequestMapping({"/","/index"})
    public String index(){
        return "index";
    }
}

你可能發(fā)現(xiàn)了,登陸沒有用文章開頭的Subject執(zhí)行登陸動作,而是直接使用action=""的表單登錄干旁。
為什么action=""呢驶沼,這是因為設置了"/**", "authc",當用戶沒有登陸時争群,所有url(/**)都會被重定向到/login商乎,而action=""或者"/login"將不被攔截,doGetAuthenticationInfo()驗證表單中的賬號密碼祭阀。

使用Subject執(zhí)行登陸動作

那如何使用Subject執(zhí)行登陸動作呢,需要使用user過濾器鲜戒。當沒有登錄時专控,user跟authc一樣,會攔截所有的url遏餐。

//user:需要已登錄或“記住我”的用戶才能訪問;
filterChainDefinitionMap.put("/**", "user");

當使用Post請求的/login登陸時伦腐,將使用Subject執(zhí)行登陸動作,doGetAuthenticationInfo()方法驗證

<form action="/login" method="post">
    <p>賬號:<input type="text" name="username" value="admin"/></p>
    <p>密碼:<input type="text" name="password" value="123456"/></p>
    <p><input type="submit" value="登錄"/></p>
</form>

@RequestMapping(value = "/login", method = RequestMethod.POST)
public String login(@RequestParam(value = "username") String userName,
                    @RequestParam(value = "password") String password) {
        UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
        } catch (AuthenticationException e) {
            e.printStackTrace();
        }
        return "index";
}

項目源碼

其余未貼出代碼請查看項目源碼https://github.com/DeppWang/SpringBoot-Demo

總結

通過ShiroFilter配置的過濾器失都,Shiro攔截所有未過濾的url柏蘑。如果未登陸,跳轉(zhuǎn)到登陸頁(loginUrl)粹庞。直接使用表單咳焚,或者使用subject.login()登陸時。在doGetAuthenticationInfo()中驗證庞溜。驗證通過革半,跳轉(zhuǎn)到成功頁(successUrl)。

當訪問需要訪問權限的url時流码,從數(shù)據(jù)庫查詢當前用戶的權限又官,最后交給Shiro框架去驗證。在doGetAuthorizationInfo()中實現(xiàn)漫试。

參考資料

springboot整合shiro-登錄認證和權限管理:http://www.ityouknow.com/springboot/2017/06/26/springboot-shiro.html
30分鐘學會如何使用Shiro:http://www.cnblogs.com/learnhow/p/5694876.html
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末六敬,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子驾荣,更是在濱河造成了極大的恐慌外构,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秘车,死亡現(xiàn)場離奇詭異典勇,居然都是意外死亡,警方通過查閱死者的電腦和手機叮趴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門割笙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事伤溉“懵耄” “怎么了?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵乱顾,是天一觀的道長板祝。 經(jīng)常有香客問我,道長走净,這世上最難降的妖魔是什么券时? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮伏伯,結果婚禮上橘洞,老公的妹妹穿的比我還像新娘。我一直安慰自己说搅,他們只是感情好炸枣,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著弄唧,像睡著了一般适肠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上候引,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天侯养,我揣著相機與錄音,去河邊找鬼澄干。 笑死沸毁,一個胖子當著我的面吹牛傻寂,可吹牛的內(nèi)容都是我干的息尺。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼疾掰,長吁一口氣:“原來是場噩夢啊……” “哼搂誉!你這毒婦竟也來了?” 一聲冷哼從身側響起静檬,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤炭懊,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拂檩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體侮腹,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年稻励,在試婚紗的時候發(fā)現(xiàn)自己被綠了父阻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愈涩。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖加矛,靈堂內(nèi)的尸體忽然破棺而出履婉,到底是詐尸還是另有隱情,我是刑警寧澤斟览,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布毁腿,位于F島的核電站,受9級特大地震影響苛茂,放射性物質(zhì)發(fā)生泄漏已烤。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一妓羊、第九天 我趴在偏房一處隱蔽的房頂上張望草戈。 院中可真熱鬧,春花似錦侍瑟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至茧球,卻和暖如春庭瑰,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抢埋。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工弹灭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人揪垄。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓穷吮,卻偏偏與公主長得像,于是被迫代替她去往敵國和親饥努。 傳聞我的和親對象是個殘疾皇子捡鱼,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容