pom:
org.apache.shiro
shiro-core
1.3.2
junit
junit
4.12
test
commons-logging
commons-logging
1.1.3
用户登入示例:
package cn.itcast.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.apache.shiro.mgt.SecurityManager;
import org.junit.Test;
public class ShiroTest01 {
/**
* 测试用户认证:
* 认证:用户登录
*
* 1.根据配置文件创建SecurityManagerFactory
* 2.通过工厂获取SecurityManager
* 3.将SecurityManager绑定到当前运行环境
* 4.从当前运行环境中构造subject
* 5.构造shiro登录的数据
* 6.主体登陆
*/
@Test
public void testLogin() {
//1.根据配置文件创建SecurityManagerFactory
Factory factory = new IniSecurityManagerFactory("classpath:shiro-test-1.ini");
//2.通过工厂获取SecurityManager
SecurityManager securityManager = factory.getInstance();
//3.将SecurityManager绑定到当前运行环境
SecurityUtils.setSecurityManager(securityManager);
//4.从当前运行环境中构造subject
Subject subject = SecurityUtils.getSubject();
//5.构造shiro登录的数据
String username = "zhangsan";
String password = "1234561";
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//6.主体登陆
subject.login(token);
//7.验证用户是否登录成功
System.out.println("用户是否登录成功="+subject.isAuthenticated());
//8.获取登录成功的数据
System.out.println(subject.getPrincipal());
}
}
shiro-test\shiro_demo\src\main\resources\shiro-test-1.ini
[users]
#模拟从数据库查询用户
#数据格式 用户名=密码
zhangsan=123456
lisi=654321
鉴权代码示例:
package cn.itcast.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Before;
import org.junit.Test;
public class ShiroTest02 {
private SecurityManager securityManager;
@Before
public void init() {
//1.根据配置文件创建SecurityManagerFactory
Factory factory = new IniSecurityManagerFactory("classpath:shiro-test-2.ini");
//2.通过工厂获取SecurityManager
SecurityManager securityManager = factory.getInstance();
//3.将SecurityManager绑定到当前运行环境
SecurityUtils.setSecurityManager(securityManager);
}
@Test
public void testLogin() {
Subject subject = SecurityUtils.getSubject();
String username = "lisi";
String password = "123456";
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
subject.login(token);
//登录成功之后,完成授权
//授权:检验当前登录用户是否具有操作权限,是否具有某个角色
System.out.println(subject.hasRole("role1"));
System.out.println(subject.isPermitted("user:save"));
}
}
shiro-test\shiro_demo\src\main\resources\shiro-test-2.ini
[users]
#用户名=密码,角色名
zhangsan=123456,role1,role2
lisi=123456,role2
[roles]
#角色
#角色名=权限列表
role1=user:save,user:update
role2=user:find
配置文件 :shiro-test\shiro_demo\src\main\resources\shiro-test-3.ini
[main]
#声明realm
permReam=cn.itcast.shiro.PermissionRealm
#注册realm到securityManager中
securityManager.realms=$permReam
自定义reaml:
package cn.itcast.shiro;
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.List;
/**
* 自定义realms对象
* 继承AuthorizingRealm
* 重写方法
* doGetAuthorizationInfo:授权
* 获取到用户的授权数据(用户的权限数据)
* doGetAuthenticationInfo:认证
* 根据用户名密码登录,将用户数据保存(安全数据)
*
*/
public class PermissionRealm extends AuthorizingRealm {
/**
* 自定义realm名称
*/
public void setName(String name) {
super.setName("permissionRealm");
}
//授权:授权的主要目的就是根据认证数据获取到用户的权限信息
/**
* principalCollection:包含了所有已认证的安全数据
* AuthorizationInfoInfo:授权数据
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行授权方法");
//1.获取安全数据 username,用户id
String username = (String)principalCollection.getPrimaryPrincipal();
//2.根据id或者名称查询用户
//3.查询用户的角色和权限信息
List perms = new ArrayList<>();
perms.add("user:save");
perms.add("user:update");
List roles = new ArrayList<>();
roles.add("role1");
roles.add("role2");
//4.构造返回
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//设置权限集合
info.addStringPermissions(perms);
//设置角色集合
info.addRoles(roles);
return info;
}
//认证:认证的主要目的,比较用户名和密码是否与数据库中的一致
//将安全数据存入到shiro进行保管
//参数:authenticationToken登录构造的usernamepasswordtoken
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行认证方法");
//1.构造uptoken
UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
//2.获取输入的用户名密码
String username = upToken.getUsername();
String password = new String(upToken.getPassword());
//3.根据用户名查询数据库,正式系统查询
//4.比较密码和数据库中的密码是否一致(密码可能需要加密)
if("123456".equals(password)) {
//5.如果成功,向shiro存入安全数据
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(username,password,getName());//1.安全数据,2.密码。3。当前realm域名称
return info;
}else{
//6.失败,抛出异常或返回null
throw new RuntimeException("用户名或密码错误");
}
}
}
ShiroTest03
package cn.itcast.shiro;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.junit.Before;
import org.junit.Test;
public class ShiroTest03 {
private SecurityManager securityManager;
@Before
public void init() {
//1.根据配置文件创建SecurityManagerFactory
Factory factory = new IniSecurityManagerFactory("classpath:shiro-test-3.ini");
//2.通过工厂获取SecurityManager
SecurityManager securityManager = factory.getInstance();
//3.将SecurityManager绑定到当前运行环境
SecurityUtils.setSecurityManager(securityManager);
}
@Test
public void testLogin() {
Subject subject = SecurityUtils.getSubject();
String username = "zhangsan";
String password = "123456";
UsernamePasswordToken token = new UsernamePasswordToken(username,password);
//执行login-->realm域中的认证方法
subject.login(token);
//授权:-->realm域中的授权方法
System.out.println(subject.hasRole("role1"));
System.out.println(subject.isPermitted("user:save"));
}
}
pom文件:
4.0.0
cn.itcast
shiro_springboot
1.0-SNAPSHOT
org.springframework.boot
spring-boot-starter-parent
2.0.5.RELEASE
UTF-8
UTF-8
1.8
1.2.47
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-data-jpa
org.springframework.boot
spring-boot-starter-logging
org.springframework.boot
spring-boot-starter-test
test
org.projectlombok
lombok
1.16.16
mysql
mysql-connector-java
org.apache.shiro
shiro-spring
1.3.2
org.apache.shiro
shiro-core
1.3.2
org.crazycake
shiro-redis
3.0.0
spring-snapshots
Spring Snapshots
https://repo.spring.io/snapshot
true
spring-milestones
Spring Milestones
https://repo.spring.io/milestone
false
spring-snapshots
Spring Snapshots
https://repo.spring.io/snapshot
true
spring-milestones
Spring Milestones
https://repo.spring.io/milestone
false
org.apache.maven.plugins
maven-compiler-plugin
3.1
${java.version}
org.apache.maven.plugins
maven-surefire-plugin
2.12.4
true
ShiroConfiguration.java 配置类:
package cn.itcast.shiro;
import cn.itcast.shiro.realm.CustomRealm;
import cn.itcast.shiro.session.CustomSessionManager;
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.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.LinkedHashMap;
import java.util.Map;
@Configuration
public class ShiroConfiguration {
//1.创建realm
@Bean
public CustomRealm getRealm() {
return new CustomRealm();
}
//2.创建安全管理器
@Bean
public SecurityManager getSecurityManager(CustomRealm realm) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(realm);
//将自定义的会话管理器注册到安全管理器中
securityManager.setSessionManager(sessionManager());
//将自定义的redis缓存管理器注册到安全管理器中
securityManager.setCacheManager(cacheManager());
return securityManager;
}
//3.配置shiro的过滤器工厂
/**
* 再web程序中,shiro进行权限控制全部是通过一组过滤器集合进行控制
*
*/
@Bean
public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
//1.创建过滤器工厂
ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
//2.设置安全管理器
filterFactory.setSecurityManager(securityManager);
//3.通用配置(跳转登录页面,为授权跳转的页面)
filterFactory.setLoginUrl("/autherror?code=1");//跳转url地址
filterFactory.setUnauthorizedUrl("/autherror?code=2");//未授权的url
//4.设置过滤器集合
/**
* 设置所有的过滤器:有顺序map
* key = 拦截的url地址
* value = 过滤器类型
*
*/
Map filterMap = new LinkedHashMap<>();
//filterMap.put("/user/home","anon");//当前请求地址可以匿名访问
//具有某中权限才能访问
//使用过滤器的形式配置请求地址的依赖权限
//filterMap.put("/user/home","perms[user-home]"); //不具备指定的权限,跳转到setUnauthorizedUrl地址
//使用过滤器的形式配置请求地址的依赖角色
//filterMap.put("/user/home","roles[系统管理员]");
filterMap.put("/user/**","authc");//当前请求地址必须认证之后可以访问
filterFactory.setFilterChainDefinitionMap(filterMap);
return filterFactory;
}
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
/**
* 1.redis的控制器,操作redis
*/
public RedisManager redisManager() {
RedisManager redisManager = new RedisManager();
redisManager.setHost(host);
redisManager.setPort(port);
return redisManager;
}
/**
* 2.sessionDao
*/
public RedisSessionDAO redisSessionDAO() {
RedisSessionDAO sessionDAO = new RedisSessionDAO();
sessionDAO.setRedisManager(redisManager());
return sessionDAO;
}
/**
* 3.会话管理器
*/
public DefaultWebSessionManager sessionManager() {
CustomSessionManager sessionManager = new CustomSessionManager();
sessionManager.setSessionDAO(redisSessionDAO());
return sessionManager;
}
/**
* 4.缓存管理器
*/
public RedisCacheManager cacheManager() {
RedisCacheManager redisCacheManager = new RedisCacheManager();
redisCacheManager.setRedisManager(redisManager());
return redisCacheManager;
}
//开启对shior注解的支持
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
application.yml 配置文件 :
server:
port: 8083
spring:
application:
name: ihrm-company #指定服务名
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/shiro_db?useUnicode=true&characterEncoding=utf8
username: root
password: root
jpa:
database: MySQL
show-sql: true
open-in-view: true
redis:
host: 127.0.0.1
port: 6379
自定义的realm:
package cn.itcast.shiro.realm;
import cn.itcast.shiro.domain.Permission;
import cn.itcast.shiro.domain.Role;
import cn.itcast.shiro.domain.User;
import cn.itcast.shiro.service.UserService;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.HashSet;
import java.util.Set;
/**
* 自定义的realm
*/
public class CustomRealm extends AuthorizingRealm {
public void setName(String name) {
super.setName("customRealm");
}
@Autowired
private UserService userService;
/**
* 授权方法
* 操作的时候,判断用户是否具有响应的权限
* 先认证 -- 安全数据
* 再授权 -- 根据安全数据获取用户具有的所有操作权限
*
*
*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//1.获取已认证的用户数据
User user = (User) principalCollection.getPrimaryPrincipal();//得到唯一的安全数据
//2.根据用户数据获取用户的权限信息(所有角色,所有权限)
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set roles = new HashSet<>();//所有角色
Set perms = new HashSet<>();//所有权限
for (Role role : user.getRoles()) {
roles.add(role.getName());
for (Permission perm : role.getPermissions()) {
perms.add(perm.getCode());
}
}
info.setStringPermissions(perms);
info.setRoles(roles);
return info;
}
/**
* 认证方法
* 参数:传递的用户名密码
*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
//1.获取登录的用户名密码(token)
UsernamePasswordToken upToken = (UsernamePasswordToken) authenticationToken;
String username = upToken.getUsername();
String password = new String( upToken.getPassword());
//2.根据用户名查询数据库
User user = userService.findByName(username);
//3.判断用户是否存在或者密码是否一致
if(user != null && user.getPassword().equals(password)) {
//4.如果一致返回安全数据
//构造方法:安全数据,密码,realm域名
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user,user.getPassword(),this.getName());
return info;
}
//5.不一致,返回null(抛出异常)
return null;
}
public static void main(String[] args) {
System.out.println(new Md5Hash("123456","wangwu",3).toString());
}
}
controller控制类:
package cn.itcast.shiro.controller;
import cn.itcast.shiro.domain.User;
import cn.itcast.shiro.service.UserService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.crypto.hash.Md5Hash;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
import java.util.Enumeration;
@RestController
public class UserController {
@Autowired
private UserService userService;
//个人主页
//使用shiro注解鉴权
//@RequiresPermissions() -- 访问此方法必须具备的权限
//@RequiresRoles() -- 访问此方法必须具备的角色
/**
* 1.过滤器:如果权限信息不匹配setUnauthorizedUrl地址
* 2.注解:如果权限信息不匹配,抛出异常
*/
@RequiresPermissions("user-home")
@RequestMapping(value = "/user/home")
public String home() {
return "访问个人主页成功";
}
//添加
@RequestMapping(value = "/user",method = RequestMethod.POST)
public String add() {
return "添加用户成功";
}
//查询
@RequestMapping(value = "/user",method = RequestMethod.GET)
public String find() {
return "查询用户成功";
}
//更新
@RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
public String update(String id) {
return "更新用户成功";
}
//删除
@RequestMapping(value = "/user/{id}",method = RequestMethod.DELETE)
public String delete() {
return "删除用户成功";
}
/**
* 1.传统登录
* 前端发送登录请求 => 接口部分获取用户名密码 => 程序员在接口部分手动控制
* 2.shiro登录
* 前端发送登录请求 => 接口部分获取用户名密码 => 通过subject.login => realm域的认证方法
*
*/
//用户登录
@RequestMapping(value="/login")
public String login(String username,String password) {
//构造登录令牌
try {
/**
* 密码加密:
* shiro提供的md5加密
* Md5Hash:
* 参数一:加密的内容
* 111111 --- abcd
* 参数二:盐(加密的混淆字符串)(用户登录的用户名)
* 111111+混淆字符串
* 参数三:加密次数
*
*/
password = new Md5Hash(password,username,3).toString();
UsernamePasswordToken upToken = new UsernamePasswordToken(username,password);
//1.获取subject
Subject subject = SecurityUtils.getSubject();
//获取session
String sid = (String) subject.getSession().getId();
//2.调用subject进行登录
subject.login(upToken);
return "登录成功";
}catch (Exception e) {
return "用户名或密码错误";
}
}
}
获取自定义sessionID:
也可以存cookie中获取sessionID,关键是要与前端约定好放cookie中还是放请求头header中
package cn.itcast.shiro.session;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.util.StringUtils;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;
/**
* 自定义的sessionManager
*/
public class CustomSessionManager extends DefaultWebSessionManager {
/**
* 头信息中具有sessionid
* 请求头:Authorization: sessionid
*
* 指定sessionId的获取方式
*/
protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
//获取请求头Authorization中的数据
String id = WebUtils.toHttp(request).getHeader("Authorization");
if(StringUtils.isEmpty(id)) {
//如果没有携带,生成新的sessionId
return super.getSessionId(request,response);
}else{
//返回sessionId;
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, "header");
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, id);
request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
return id;
}
}
}
演示代码下载地址:
https://pan.baidu.com/s/18B5PCLVBABi8G1P7raK5vg
数据库表在项目里
实体类:
注意:使用shiro与Redis的整合插件包,实体类必须实现一个接口AuthCachePrincipal才能将数据存入Redis中,重写的方法回null就可以了;
package cn.itcast.shiro.domain;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.Setter;
import org.crazycake.shiro.AuthCachePrincipal;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
/**
* 用户实体类
*/
@Entity
@Table(name = "pe_user")
@Getter
@Setter
/**
* AuthCachePrincipal:
* redis和shiro插件包提供的接口
*/
public class User implements Serializable ,AuthCachePrincipal {
private static final long serialVersionUID = 4297464181093070302L;
/**
* ID
*/
@Id
private String id;
private String username;
private String password;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name="pe_user_role",joinColumns={@JoinColumn(name="user_id",referencedColumnName="id")},
inverseJoinColumns={@JoinColumn(name="role_id",referencedColumnName="id")}
)
private Set roles = new HashSet();//用户与角色 多对多
@Override
public String getAuthCacheKey() {
return null;
}
}
5.shiro中的过滤器
Filter |
解释 |
anon |
无参,开放权限,可以理解为匿名用户或游客 |
authc |
无参,需要认证 |
logout |
无参,注销,执行后会直接跳转到 shiroFilterFactoryBean.setLoginUrl(); 设置的 url |
authcBasic |
无参,表示 httpBasic 认证 |
user |
无参,表示必须存在用户,当登入操作时不做检查 |
ssl |
无参,表示安全的URL请求,协议为 https |
perms[user] |
参数可写多个,表示需要某个或某些权限才能通过,多个参数时写 perms[“user, admin”],当有多个参数时必须每个参数都通过才算通过 |
roles[admin] |
参数可写多个,表示是某个或某些角色才能通过,多个参数时写 roles[“admin,user”], 当有多个参数时必须每个参数都通过才算通过 |
rest[user] |
根据请求的方法,相当于 perms[user:method],其中 method 为 post,get,delete 等 |
port[8081] |
当请求的URL端口不是8081时,跳转到当前访问主机HOST的8081端口 |
注意:anon, authc, authcBasic, user 是第一组认证过滤器,perms, port, rest, roles, ssl 是第二组授权过滤器,要通过授权过滤器,就先要完成登陆认证操作(即先要完成认证才能前去寻找授权) 才能走第二组授权器
(例如访问需要 roles 权限的 url,如果还没有登陆的话,会直接跳转到
shiroFilterFactoryBean.setLoginUrl(); 设置的 url )