Shiro安全框架
1.简介
- Apache Shiro 是一个功能强大且易于使用的 Java 安全(权限)框架。
- Shiro 可以完成:认证、授权、加密、会话管理、与 Web 集成、缓存等。借助 Shiro 您可以快速轻松地保护任何应用程序,从最小的移动应用程序到最大的 Web 和企业应用程序。
2.使用Shiro的好处
- 易于使用:Shiro构建系统安全框架非常简单,易于上手掌握。
- 全面:Shiro 包含系统安全框架需要的功能,满足安全需求的“一站式服务”。
- 适应灵活:在任何程序都可以应用,也没有强制要求任何规范,甚至没有很多依赖项。
- 兼容性强:Shiro 的设计模式使其易于与其他框架和应用程序集成。Shiro 与 Spring、Grails、Wicket、Tapestry、Mule、Apache Camel、Vaadin 等框架无缝集成。
- Shiro 是 Apache 软件基金会的一个开源项目,有完备的社区支持,文档支持。
3.三大核心
Subject
应用代码直接交互的对象是 Subject,是Shiro 的对外 API 核心,代表“当前操作的用户”,这个用户不仅仅指的是人,也可以是其他交互的事物。
SecurityManager
安全管理器;即所有与安全有关的操作都会与 SecurityManager交互;且其管理着所有 Subject、并负责进 行认证、授权、会话及缓存的管理 ;可以看出它是 Shiro 的核心。
Realm
Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法;也需要从 Realm 得到用户相应的角色/ 权限进行验证用户是否能进行操作;可以把 Realm 看成 DataSource。
4.Shiro 应用实例
4.1.开发组件
名称 |
版本 |
HighGoDB |
安全版V4.5、企业版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.工程结构示例
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注解权限会失效--> <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> <!--数据源相关--> <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; }
//权限管理,配置主要是Realm的管理认证 @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; }
//Filter工厂,设置对应的过滤条件和跳转条件 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) { ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/login"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/index");
// 权限控制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;
//实现AuthorizingRealm接口用户用户认证 public class MyShiroRealm extends AuthorizingRealm {
//角色权限和对应权限添加 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //添加角色和权限 SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); System.out.println(principalCollection.getPrimaryPrincipal()); TUser user= (TUser) principalCollection.getPrimaryPrincipal(); //保存所有角色名 Set<String> allRoles = new HashSet<>(); //保存所有权限名 Set<String> allPermissions = new HashSet<>(); //查询对应角色 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());
//查询所有权限 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、登录认证的方法需要先执行,需要用他来判断登录的用户信息是否合法 String username = (String) token.getPrincipal(); // 取得用户名 // 需要通过用户名取得用户的完整信息,利用业务层操作 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()); // 将数据库中的密码与输入的密码进行比较,这样就可以确定当前用户是否可以正常登录 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("登录成功,即将进行页面跳转"); 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.登录结果展示