Shiro安全框架
1.簡介
- Apache Shiro 是一個功能強大且易于使用的 Java 安全(權(quán)限)框架。
- Shiro 可以完成:認證、授權(quán)、加密、會話管理、與 Web 集成、緩存等。借助 Shiro 您可以快速輕松地保護任何應(yīng)用程序,從最小的移動應(yīng)用程序到最大的 Web 和企業(yè)應(yīng)用程序。
2.使用Shiro的好處
- 易于使用:Shiro構(gòu)建系統(tǒng)安全框架非常簡單,易于上手掌握。
- 全面:Shiro 包含系統(tǒng)安全框架需要的功能,滿足安全需求的“一站式服務(wù)”。
- 適應(yīng)靈活:在任何程序都可以應(yīng)用,也沒有強制要求任何規(guī)范,甚至沒有很多依賴項。
- 兼容性強:Shiro 的設(shè)計模式使其易于與其他框架和應(yīng)用程序集成。Shiro 與 Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin 等框架無縫集成。
- Shiro 是 Apache 軟件基金會的一個開源項目,有完備的社區(qū)支持,文檔支持。
3.三大核心
Subject
應(yīng)用代碼直接交互的對象是 Subject,是Shiro 的對外 API 核心,代表“當前操作的用戶”,這個用戶不僅僅指的是人,也可以是其他交互的事物。
SecurityManager
安全管理器;即所有與安全有關(guān)的操作都會與 SecurityManager交互;且其管理著所有 Subject、并負責(zé)進 行認證、授權(quán)、會話及緩存的管理 ;可以看出它是 Shiro 的核心。
Realm
Shiro 從 Realm 獲取安全數(shù)據(jù)(如用戶、角色、權(quán)限),就是說SecurityManager 要驗證用戶身份,那么它需要從 Realm 獲取相應(yīng)的用戶進行比較以確定用戶身份是否合法;也需要從 Realm 得到用戶相應(yīng)的角色/ 權(quán)限進行驗證用戶是否能進行操作;可以把 Realm 看成 DataSource。
4.Shiro 應(yīng)用實例
4.1.開發(fā)組件
名稱 |
版本 |
HighGoDB |
安全版V4.5、企業(yè)版V5及以上 |
HgdbJdbc |
6.2.4 |
JDK |
1.6、1.7、1.8 |
Java IDE |
Eclipse、IntelliJ IDEA |
SpringBoot |
1.5.9.RELEASE |
MybatisPlus |
2.1.4 |
Druid |
1.2.6 |
Shiro |
1.3.2 |
4.2.工程結(jié)構(gòu)示例

4.3.主要文件
pom.xml
<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.5.9.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent>
<properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <mybatisplus-spring-boot-starter.version>1.0.5</mybatisplus-spring-boot-starter.version> <mybatisplus.version>2.1.4</mybatisplus.version> <shiro-spring.version>1.3.2</shiro-spring.version> <druid.version>1.2.6</druid.version> </properties>
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--沒有此依賴shiro注解權(quán)限會失效--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </dependency>
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-freemarker</artifactId> </dependency> <!--lombok插件使用依賴--> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!--mybatis plus依賴--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatisplus-spring-boot-starter</artifactId> <version>${mybatisplus-spring-boot-starter.version}</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>${mybatisplus.version}</version> </dependency> <!--數(shù)據(jù)源相關(guān)--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>${druid.version}</version> </dependency>
<dependency> <groupId>com.highgo</groupId> <artifactId>HgdbJdbc</artifactId> <version>6.2.4</version> </dependency> <!--shiro依賴--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>${shiro-spring.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--熱部署--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>springloaded</artifactId> </dependency> </dependencies>
|
application.yml
server: port: 8080 spring: datasource: type: com.alibaba.druid.pool.DruidDataSource driver-class-name: com.highgo.jdbc.Driver url: jdbc:highgo://xxxx:5866/highgo?currentSchema=shiro_demo username: sysdba password: xxxx druid: initial-size: 5 min-idle: 5 max-active: 20 max-wait: 60000 min-evictable-idle-time-millis: 30000 validation-query: select 1 test-on-borrow: false test-on-return: false filters: stat,wall,log4j db-type: postgresql mybatis-plus: mapper-locations: classpath:mappers/*.xml type-aliases-package: com.highgo.entity global-config: db-column-underline: true configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
ShiroConfig.java
package com.highgo.shiro;
import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
@Configuration public class ShiroConfig {
//將自己的驗證方式加入容器 @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); return myShiroRealm; }
//權(quán)限管理,配置主要是Realm的管理認證 @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; }
//Filter工廠,設(shè)置對應(yīng)的過濾條件和跳轉(zhuǎn)條件 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必須設(shè)置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不設(shè)置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 shiroFilterFactoryBean.setLoginUrl("/login"); // 登錄成功后要跳轉(zhuǎn)的鏈接 shiroFilterFactoryBean.setSuccessUrl("/index");
// 權(quán)限控制map. LinkedHashMap<String, String> filterChainDefinitionMap=new LinkedHashMap<>(); filterChainDefinitionMap.put("/css/**", "anon"); //表示可以匿名訪問 filterChainDefinitionMap.put("/js/**", "anon"); //表示可以匿名訪問 filterChainDefinitionMap.put("/img/**", "anon"); //表示可以匿名訪問 filterChainDefinitionMap.put("/font/**", "anon"); //表示可以匿名訪問 filterChainDefinitionMap.put("/images/**", "anon"); //表示可以匿名訪問 filterChainDefinitionMap.put("/login", "anon"); //表示可以匿名訪問 filterChainDefinitionMap.put("/user_login", "anon"); filterChainDefinitionMap.put("/logout*","logout"); filterChainDefinitionMap.put("/error","anon"); filterChainDefinitionMap.put("/**","authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; }
//加入注解的使用,不加入這個注解不生效 @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { System.out.println("開啟了shiro注解功能"); AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
|
MyShiroRealm.java
package com.highgo.shiro;
import com.baomidou.mybatisplus.mapper.EntityWrapper; import com.highgo.entity.*; import org.apache.shiro.authc.*; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection;
import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set;
//實現(xiàn)AuthorizingRealm接口用戶用戶認證 public class MyShiroRealm extends AuthorizingRealm {
//角色權(quán)限和對應(yīng)權(quán)限添加 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //添加角色和權(quán)限 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); System.out.println(principalCollection.getPrimaryPrincipal()); TUser user= (TUser) principalCollection.getPrimaryPrincipal(); //保存所有角色名 Set<String> allRoles = new HashSet<>(); //保存所有權(quán)限名 Set<String> allPermissions = new HashSet<>(); //查詢對應(yīng)角色 List<TUserRole> secUserRoles = new TUserRole().selectList(new EntityWrapper().eq("user_id", user.getId())); for (TUserRole userRole:secUserRoles) { TRole role = new TRole(); role.setId(userRole.getRoleId()); role = role.selectById(); allRoles.add(role.getName());
//查詢所有權(quán)限 List<TPermission> permissions = new ArrayList<>(); List<TRolePermission> rolePermissions = new TRolePermission().selectList(new EntityWrapper().eq("role_id", role.getId())); for (TRolePermission rolePermission:rolePermissions) { TPermission permission = new TPermission(); permission.setId(rolePermission.getPermissionId()); permission = permission.selectById(); allPermissions.add(permission.getName()); } } //添加角色 simpleAuthorizationInfo.addRoles(allRoles); simpleAuthorizationInfo.addStringPermissions(allPermissions);
System.out.println(user);
return simpleAuthorizationInfo; }
//用戶認證 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 1、登錄認證的方法需要先執(zhí)行,需要用他來判斷登錄的用戶信息是否合法 String username = (String) token.getPrincipal(); // 取得用戶名 // 需要通過用戶名取得用戶的完整信息,利用業(yè)務(wù)層操作 TUser user = null; try { user = new TUser().selectOne(new EntityWrapper().eq("username",username)); } catch (Exception e) { e.printStackTrace(); } if (user == null) { throw new UnknownAccountException("該用戶名稱不存在!"); } else { // 進行密碼的驗證處理 String password =new String((char[]) token.getCredentials()); // 將數(shù)據(jù)庫中的密碼與輸入的密碼進行比較,這樣就可以確定當前用戶是否可以正常登錄 if (user.getPassword().equals(password)) { // 密碼正確
AuthenticationInfo auth = new SimpleAuthenticationInfo(user, password, "memberRealm"); return auth; } else { throw new IncorrectCredentialsException("密碼錯誤!"); } } } }
|
LoginController.java
package com.highgo.controller;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.apache.shiro.authz.annotation.RequiresRoles; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpSession;
@Controller public class LoginController {
@GetMapping(value = "/login") public String login(){ return "login"; }
@PostMapping(value = "/user_login") public String login(@RequestParam String username, @RequestParam String password, Model model){ try { model.addAttribute("username", username); UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(username, password); Subject subject = SecurityUtils.getSubject(); //完成登錄 subject.login(usernamePasswordToken); System.out.println("登錄成功,即將進行頁面跳轉(zhuǎn)"); return "redirect:/index"; } catch (Exception e) { String ex = e.getClass().getName(); if (ex != null) { if (UnknownAccountException.class.getName().equals(ex)) { System.out.println("用戶名不存在"); } else if (IncorrectCredentialsException.class.getName().equals(ex)) { System.out.println("賬戶或密碼錯誤"); } else { System.out.println("未知錯誤"); } } //返回登錄頁面 return "login"; } }
@ResponseBody @GetMapping(value = "/index") public String index(){ return "welcome"; } }
|
4.4.登錄結(jié)果展示
