Shiro入门
1. 概念
-
Apache Shiro是一个java的安全(权限)框架,可以非常容易地开发出足够号地应用,其不仅可以用在JAVASE环境,也可以用在JAVAEE环境,可以完成认证、授权、加密、会话管理、web集成、缓存等;
-
三大组件
- Subject:代表当前主体,与当前应用交互的任何东西都是subject;
- SecurityManager:安全管理器,所有有关安全的操作均与此有关。是shiro核心,负责与其他组件交互;
- Realm:Shiro 从 Realm 获取安全数据(如用户、角色、权限),就是说 SecurityManager 要验证用户身份,那么它需要从 Realm 获取相应的用户进行比较以确定用户身份是否合法,也需要从 Realm 得到用户相应的角色/权限进行验证用户是否能进行操作;
-
大概流程为,当用户前台登录发起请求时:
-
从shiro中获取subject主体;
SecurityUtils.getSubject();
-
判断当前用户是否认证过了,如果认证过了就放行了;
subject.isAuthenticated()
-
如果没有认证过,就把前台传递的账号密码封装为一个UserNamePasswordToken对象;
new UsernamePasswordToken(username, password);
-
把UserNamePasswordToken对象传入,进行登录操作;
subject.login(usernamepasswordtoken);
-
我们配置的安全管理器中实现了doGetAuthenticationInfo方法,从数据库查询用户数据,加密加盐后进行shiro的认证;
-
如果认证成功,进行权限赋于;
-
根据配置shiro的真实过滤器跳转至登录成功页面或登录失败的页面;
-
2. 官方案例入门
(可粗略看下~)
- 导入依赖
<dependencies>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>1.4.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.5.6</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
</dependencies>
-
resources配置文件:
- log4j日志文件:
log4j.rootLogger=INFO, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n # General Apache libraries log4j.logger.org.apache=WARN # Spring log4j.logger.org.springframework=WARN # Default Shiro logging log4j.logger.org.apache.shiro=INFO # Disable verbose logging log4j.logger.org.apache.shiro.util.ThreadContext=WARN log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
- shiro配置文件(shiro.ini)
[users] # user 'root' with password 'secret' and the 'admin' role root = secret, admin # user 'guest' with the password 'guest' and the 'guest' role guest = guest, guest # user 'presidentskroob' with password '12345' ("That's the same combination on # my luggage!!!" ;)), and role 'president' presidentskroob = 12345, president # user 'darkhelmet' with password 'ludicrousspeed' and roles 'darklord' and 'schwartz' darkhelmet = ludicrousspeed, darklord, schwartz # user 'lonestarr' with password 'vespa' and roles 'goodguy' and 'schwartz' lonestarr = vespa, goodguy, schwartz # ----------------------------------------------------------------------------- # Roles with assigned permissions # # Each line conforms to the format defined in the # org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc # ----------------------------------------------------------------------------- [roles] # 'admin' role has all permissions, indicated by the wildcard '*' admin = * # The 'schwartz' role can do anything (*) with any lightsaber: schwartz = lightsaber:* # The 'goodguy' role is allowed to 'drive' (action) the winnebago (type) with # license plate 'eagle5' (instance specific id) goodguy = winnebago:drive:eagle5
-
官方入门案例
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.mgt.DefaultSecurityManager;
import org.apache.shiro.realm.text.IniRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class Quickstart {
private static final transient Logger log = LoggerFactory.getLogger(Quickstart.class);
public static void main(String[] args) {
DefaultSecurityManager securityManager = new DefaultSecurityManager();
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
securityManager.setRealm(iniRealm);
SecurityUtils.setSecurityManager(securityManager);
//获取当前用户对象
Subject currentUser = SecurityUtils.getSubject();
//通过当前用户拿到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 + "]");
}
//判断当前的用户是否被认证
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 more exceptions here (maybe custom ones specific to your application?
catch (AuthenticationException ae) {
//unexpected condition? error?
}
}
//获得当前用户的认证
log.info("User [" + currentUser.getPrincipal() + "] logged in successfully.");
//test a role:
if (currentUser.hasRole("schwartz")) {
log.info("May the Schwartz be with you!");
} else {
log.info("Hello, mere mortal.");
}
//粗粒度
if (currentUser.isPermitted("lightsaber:wield")) {
log.info("You may use a lightsaber ring. Use it wisely.");
} else {
log.info("Sorry, lightsaber rings are for schwartz masters only.");
}
//细粒度
if (currentUser.isPermitted("winnebago:drive:eagle5")) {
log.info("You are permitted to 'drive' the winnebago with license plate (id) 'eagle5'. " +
"Here are the keys - have fun!");
} else {
log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
}
//注销
currentUser.logout();
//结束
System.exit(0);
}
}
3. 整合springboot-mybatis-druid-thymeleaf
- 导入依赖
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!--lombok-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<!--druid-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.7.0</version>
</dependency>
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
- 实现shiro认证授权,先编写四个测试页面,包含首页,登录页,修改页和添加页,只有通过数据库认证通过的用户,在shiro的相应授权下才能进入修改页或添加页,没通过则进入提示页;
- 项目目录:
- 首先编写配置类,使用UserRealm、DefaultWebSecurityManager、ShiroFilterFactoryBean三个类分别实现链接数据,默认的安全管理器和过滤器;
package com.config;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfig {
//ShiroFilterFactoryBean(第三步)
@Bean
public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("manager") DefaultWebSecurityManager defaultWebSecurityManager){
ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();
//设置安全管理器
factoryBean.setSecurityManager(defaultWebSecurityManager);
/*
* anon:无需认证即可访问
* authc:必须认证了才能访问
* user:必须拥有‘记住我’功能才能使用
* perms:拥有某个资源的权限才能使用
* role:拥有某个角色权限才能使用
* */
//拦截
Map<String, String> filterMap = new LinkedHashMap<>();
//设置权限
filterMap.put("/user/add","perms[user:add]");
filterMap.put("/user/update","perms[user:update]");
//设置登录页面路径
factoryBean.setLoginUrl("/toLogin");
factoryBean.setUnauthorizedUrl("/noauth");
factoryBean.setFilterChainDefinitionMap(filterMap);
return factoryBean;
}
//DefaultWebSecurityManager(第二步)
@Bean(name = "manager")
public DefaultWebSecurityManager getDefaultWebSecurityManager(@Qualifier("realm") UserRealm userRealm){
DefaultWebSecurityManager webSecurityManager = new DefaultWebSecurityManager();
//关联UserRealm
webSecurityManager.setRealm(userRealm);
return webSecurityManager;
}
//创建realm对象,需要自定义类(第一步)
@Bean(name = "realm")
public UserRealm userRealm(){
return new UserRealm();
}
}
- 在路由跳转中判断用户信息
package com.config;
import com.pojo.User;
import com.service.UserService;
import org.apache.shiro.SecurityUtils;
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 org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=>授权doGetAuthorizationInfo");
//拿到当前登录的用户信息
Subject subject = SecurityUtils.getSubject();
//获取到的User是认证中的user
User currentUser = (User) subject.getPrincipal();
//授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission(currentUser.getPerms());
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了=>认证doGetAuthorizationInfo");
//token类型转换
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//连接真实数据库
User user = userService.queryUserByName(token.getUsername());
if (user==null){
return null;
}
//密码认证shiro做
return new SimpleAuthenticationInfo(user,user.getUser_pwd(),"");
}
}
- 编写realm类,实现认证和授权
package com.config;
import com.pojo.User;
import com.service.UserService;
import org.apache.shiro.SecurityUtils;
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 org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
public class UserRealm extends AuthorizingRealm {
@Autowired
UserService userService;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了=>授权doGetAuthorizationInfo");
//拿到当前登录的用户信息
Subject subject = SecurityUtils.getSubject();
//获取到的User是认证中的user
User currentUser = (User) subject.getPrincipal();
//授权
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addStringPermission(currentUser.getPerms());
return info;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了=>认证doGetAuthorizationInfo");
//token类型转换
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//连接真实数据库
User user = userService.queryUserByName(token.getUsername());
if (user==null){
return null;
}
//密码认证shiro做
return new SimpleAuthenticationInfo(user,user.getUser_pwd(),"");
}
}
- shiro整合thymeleaf
- 导入依赖
<!--shiro-thymeleaf-->
<dependency>
<groupId>com.github.theborakompanioni</groupId>
<artifactId>thymeleaf-extras-shiro</artifactId>
<version>2.0.0</version>
</dependency>
- 页面代码如下:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页</h1>
<p th:text="${msg}"></p>
<!--登录成功就不显示了-->
<p shiro:guest="true">
<a th:href="@{/toLogin}">登录</a>
</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>