SpringBoot集成Shiro思路以及代码过程
模型
假设存在用户(User)、角色(Role)、权限(Permission)3中模型。依赖关系如下:
用户分配角色,角色分配权限。用户不直接参与权限的分配。
SpringBoot集成Shiro流程
demo的代码架构如下:
创建IO模型
User存在:1、唯一ID 2、用户名 3、密码 4、拥有的Role
public class User {
private Integer uid;
private String username;
private String password;
private Set<Role> roles = new HashSet<>();
}
Role存在:1、唯一ID 2、角色名 3、角色拥有的权限 4、拥有该角色的用户(可以不需要)
public class Role {
private Integer rid;
private String rname;
private Set<Permission> permissions = new HashSet<>();
private Set<User> users = new HashSet<>();
}
Permission存在:1、唯一ID 2、权限名 3、。。。
public class Permission {
private Integer pid;
private String name;
private String url;
}
数据库sql如下:
-- 权限表 --
CREATE TABLE permission (
pid int(11) NOT NULL AUTO_INCREMENT,
name VARCHAR(255) NOT NULL DEFAULT '',
url VARCHAR(255) DEFAULT '',
PRIMARY KEY (pid)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
INSERT INTO permission VALUES ('1', 'add', '');
INSERT INTO permission VALUES ('2', 'delete', '');
INSERT INTO permission VALUES ('3', 'edit', '');
INSERT INTO permission VALUES ('4', 'query', '');
-- 用户表 --
CREATE TABLE user(
uid int(11) NOT NULL AUTO_INCREMENT,
username VARCHAR(255) NOT NULL DEFAULT '',
password VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (uid)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
INSERT INTO user VALUES ('1', 'admin', '123');
INSERT INTO user VALUES ('2', 'demo', '123');
-- 角色表 --
CREATE TABLE role(
rid int(11) NOT NULL AUTO_INCREMENT,
rname VARCHAR(255) NOT NULL DEFAULT '',
PRIMARY KEY (rid)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
INSERT INTO role VALUES ('1', 'admin');
INSERT INTO role VALUES ('2', 'customer');
-- 权限角色关系表 --
CREATE TABLE permission_role (
rid int(11) NOT NULL ,
pid int(11) NOT NULL ,
KEY idx_rid (rid),
KEY idx_pid (pid)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
INSERT INTO permission_role VALUES ('1', '1');
INSERT INTO permission_role VALUES ('1', '2');
INSERT INTO permission_role VALUES ('1', '3');
INSERT INTO permission_role VALUES ('1', '4');
INSERT INTO permission_role VALUES ('2', '1');
INSERT INTO permission_role VALUES ('2', '4');
-- 用户角色关系表 --
CREATE TABLE user_role (
uid int(11) NOT NULL ,
rid int(11) NOT NULL ,
KEY idx_uid (uid),
KEY idx_rid (rid)
) ENGINE = InnoDB DEFAULT CHARSET = utf8;
INSERT INTO user_role VALUES (1, 1);
INSERT INTO user_role VALUES (2, 2);
Mapper的配置
1、UserMapper
根据用户名去数据库查找实体
public interface UserMapper {
User findByUsername(@Param("username") String username);
}
2、service配置存在如下关系
3、mapper.xml
mapper中配置好各个IO的对应情况
需要配置读取UserMapper.xml的路径
application中需要配置需要加载的mapper类路径
授权与鉴权
需要继承 import org.apache.shiro.realm.AuthorizingRealm; 实现当中的方法。
1、第一个方法为授权
2、第二个方法为登陆
1、授权:
核心思想:
- 获取当前用户信息
- 因为user存放的是set<Role>,所以用for循环取出内容
- 取出的role存放new 出来的List中
- 利用for循环取出permission放入new出来的List中
- 将刚才的list放入 new SimpleAuthorizationInfo() 中的role和permission中
- 返回该对象
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals)
{
// 获取User用户
User user = (User) principals.fromRealm(this.getClass().getName()).iterator().next();
List<String> permissionList = new ArrayList<>();
List<String> roleNameList = new ArrayList<>();
Set<Role> roleSet = user.getRoles();
if (roleSet != null)
{
for (Role role : roleSet)
{
roleNameList.add(role.getRname());
Set<Permission> permissionSet = role.getPermissions();
if (permissionSet != null)
{
for (Permission permission : permissionSet)
{
permissionList.add(permission.getName());
}
}
}
}
// 需要把角色和权限放入info中
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 权限设定
info.addStringPermissions(permissionList);
// 角色设定
info.addRoles(roleNameList);
return info;
}
1、登陆:
核心思想:
- token中获取当前用户信息
- 因为user存放的是set<Role>,所以用for循环取出内容
- 取出用户名。用户名和密码是byte存放,可以考虑用new String转换。
- 返回SimpleAuthenticationInfo对象。包括用户实体,密码,该对象名
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException
{
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
String username = usernamePasswordToken.getUsername();
User user = userService.findByUsername(username);
return new SimpleAuthenticationInfo(user, user.getPassword(), this.getClass().getName());
}
校验重写设定
继承SimpleCredentialsMatcher类重写校验方法
校验方法可以自己设定,例如:加密盐(具体需要网上看教程)
这里只是做简单的对比并且返回对比结果。
public class CredentialMatcher extends SimpleCredentialsMatcher
{
@Override
public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info)
{
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
String password = new String(usernamePasswordToken.getPassword());
String dbPassword = (String) info.getCredentials();
return this.equals(password, dbPassword);
}
}
ShiroConfiguration的配置
@Configuration
public class ShiroConfiguration
{
@Bean("shiroFilter")
public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager manager)
{
ShiroFilterFactoryBean bean = new ShiroFilterFactoryBean();
bean.setSecurityManager(manager);
// 登陆界面
bean.setLoginUrl("/login");
// 成功登陆后的界面
bean.setSuccessUrl("/index");
// 没有权限访问的界面
bean.setUnauthorizedUrl("/unauthorized");
// 定制相关表单是否需要相关权限的设定,具体配置信息请看:Shiro-内置的FilterChain
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//注意过滤器配置顺序 不能颠倒这里是按照顺序判断的
// index界面需要鉴权
filterChainDefinitionMap.put("/index", "authc");
// login、loginUser表单不需要验证
filterChainDefinitionMap.put("/login", "anon");
filterChainDefinitionMap.put("/loginUser", "anon");
// admin表单需要角色 admin 才能访问
filterChainDefinitionMap.put("/admin", "roles[admin]");
// edit表单需要权限 edit 才能访问
filterChainDefinitionMap.put("/edit", "perms[edit]");
filterChainDefinitionMap.put("/druid/**", "anon");
filterChainDefinitionMap.put("/**", "user");
bean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return bean;
}
@Bean("securityManager")
public SecurityManager securityManager(@Qualifier("authRealm") AuthRealm authRealm)
{
DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
manager.setRealm(authRealm);
return manager;
}
@Bean("authRealm")
public AuthRealm authRealm()
{
AuthRealm authRealm = new AuthRealm();
/**
* shiro自带的MemoryConstrainedCacheManager作缓存只能用于本机,那么在集群时就无法使用,
* 如果使用ehcache、redis等其他缓存,可以参考https://www.cnblogs.com/lic309/p/4072848.html
*/
authRealm.setCacheManager(new MemoryConstrainedCacheManager());
// 用com.mmall.demo2.CredentialMatcher中自定义的密码比较器对密码进行比较
authRealm.setCredentialsMatcher(new CredentialMatcher());
return authRealm;
}
// 把shiro和spring进行绑定
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(@Qualifier("securityManager") SecurityManager securityManager)
{
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
// 开启自动代码,设置为true即可
@Bean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator()
{
DefaultAdvisorAutoProxyCreator creator = new DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
}
其他代码展示
Controller代码
@Controller
public class TestController
{
@RequestMapping("login")
public String login()
{
return "login";
}
@RequestMapping("/index")
public String index()
{
return "index";
}
@RequestMapping("/logout")
public String logout()
{
// 先验证主体
Subject subject = SecurityUtils.getSubject();
if (subject != null)
{
subject.logout();
}
return "login";
}
@RequestMapping("unauthorized")
public String unauthorized()
{
return "unauthorized";
}
@RequestMapping("/admin")
@ResponseBody
public String admin()
{
return "admin success";
}
@RequestMapping("/edit")
@ResponseBody
public String edit()
{
return "edit success";
}
@RequestMapping("/loginUser")
public String loginUser(@RequestParam("username") String username,
@RequestParam("password") String password,
HttpSession session)
{
// 初始化这个用户的token
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
// 获取事件的主体
Subject subject = SecurityUtils.getSubject();
try
{
// 尝试登录
subject.login(token);
// 获取用户的全部信息
User user = (User) subject.getPrincipal();
// 用于界面输出
session.setAttribute("user", user);
return "index";
}
catch (Exception e)
{
return "login";
}
}
}
application.properties代码
##蓝色加粗字体需自行修改
## database ##
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/fys?characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
## mybatis ##
mybatis.mapper-locations=mappers/*.xml
#mybatis.type-aliases-package=com.mmall.demo2.model
## jsp ##
spring.mvc.view.prefix=/pages/
spring.mvc.view.suffix=.jsp
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Home</title>
</head>
<body>
<h1> 欢迎登录, ${user.username} </h1>
</body>
</html>
login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Login</title>
</head>
<body>
<h1> 欢迎登录 </h1>
<form action="/loginUser" method="post">
<input type="text" name="username"> <br>
<input type="password" name="password"> <br>
<input type="submit" value="提交">
</form>
</body>
</html>
unauthorized.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Unauthorized</title>
</head>
<body>
Unauthorized!
</body>
</html>
FilerChain的方法解释:
- /**
- * Shiro-1.2.2内置的FilterChain
- * @see =============================================================================================================================
- * @see 1)Shiro验证URL时,URL匹配成功便不再继续匹配查找(所以要注意配置文件中的URL顺序,尤其在使用通配符时)
- * @see 故filterChainDefinitions的配置顺序为自上而下,以最上面的为准
- * @see 2)当运行一个Web应用程序时,Shiro将会创建一些有用的默认Filter实例,并自动地在[main]项中将它们置为可用
- * @see 自动地可用的默认的Filter实例是被DefaultFilter枚举类定义的,枚举的名称字段就是可供配置的名称
- * @see anon---------------org.apache.shiro.web.filter.authc.AnonymousFilter
- * @see authc--------------org.apache.shiro.web.filter.authc.FormAuthenticationFilter
- * @see authcBasic---------org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
- * @see logout-------------org.apache.shiro.web.filter.authc.LogoutFilter
- * @see noSessionCreation--org.apache.shiro.web.filter.session.NoSessionCreationFilter
- * @see perms--------------org.apache.shiro.web.filter.authz.PermissionAuthorizationFilter
- * @see port---------------org.apache.shiro.web.filter.authz.PortFilter
- * @see rest---------------org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
- * @see roles--------------org.apache.shiro.web.filter.authz.RolesAuthorizationFilter
- * @see ssl----------------org.apache.shiro.web.filter.authz.SslFilter
- * @see user---------------org.apache.shiro.web.filter.authz.UserFilter
- * @see =============================================================================================================================
- * @see 3)通常可将这些过滤器分为两组
- * @see anon,authc,authcBasic,user是第一组认证过滤器
- * @see perms,port,rest,roles,ssl是第二组授权过滤器
- * @see 注意user和authc不同:当应用开启了rememberMe时,用户下次访问时可以是一个user,但绝不会是authc,因为authc是需要重新认证的
- * @see user表示用户不一定已通过认证,只要曾被Shiro记住过登录状态的用户就可以正常发起请求,比如rememberMe
- * @see 说白了,以前的一个用户登录时开启了rememberMe,然后他关闭浏览器,下次再访问时他就是一个user,而不会authc
- * @see =============================================================================================================================
- * @see 4)举几个例子
- * @see /admin=authc,roles[admin] 表示用户必需已通过认证,并拥有admin角色才可以正常发起'/admin'请求
- * @see /edit=authc,perms[admin:edit] 表示用户必需已通过认证,并拥有admin:edit权限才可以正常发起'/edit'请求
- * @see /home=user 表示用户不一定需要已经通过认证,只需要曾经被Shiro记住过登录状态就可以正常发起'/home'请求
- * @see =============================================================================================================================
- * @see 5)各默认过滤器常用如***意URL Pattern里用到的是两颗星,这样才能实现任意层次的全匹配)
- * @see /admins/**=anon 无参,表示可匿名使用,可以理解为匿名用户或游客
- * @see /admins/user/**=authc 无参,表示需认证才能使用
- * @see /admins/user/**=authcBasic 无参,表示httpBasic认证
- * @see /admins/user/**=user 无参,表示必须存在用户,当登入操作时不做检查
- * @see /admins/user/**=ssl 无参,表示安全的URL请求,协议为https
- * @see /admins/user/**=perms[user:add:*]
- * @see 参数可写多个,多参时必须加上引号,且参数之间用逗号分割,如/admins/user/**=perms["user:add:*,user:modify:*"]
- * @see 当有多个参数时必须每个参数都通过才算通过,相当于isPermitedAll()方法
- * @see /admins/user/**=port[8081]
- * @see 当请求的URL端口不是8081时,跳转到schemal://serverName:8081?queryString
- * @see 其中schmal是协议http或https等,serverName是你访问的Host,8081是Port端口,queryString是你访问的URL里的?后面的参数
- * @see /admins/user/**=rest[user]
- * @see 根据请求的方法,相当于/admins/user/**=perms[user:method],其中method为post,get,delete等
- * @see /admins/user/**=roles[admin]
- * @see 参数可写多个,多个时必须加上引号,且参数之间用逗号分割,如/admins/user/**=roles["admin,guest"]
- * @see 当有多个参数时必须每个参数都通过才算通过,相当于hasAllRoles()方法
- * @see =============================================================================================================================
- * @create Sep 29, 2013 5:24:49 PM
- * @author 玄玉<http://blog.csdn.net/jadyer>
- */
我截取一段关于shiro.xml中关于ShiroFilterFactoryBean配置的说明,如下:
securityManager:这个属性是必须的。
loginUrl:没有登录的用户请求需要登录的页面时自动跳转到登录页面,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。
successUrl:登录成功默认跳转页面,不配置则跳转至”/”。如果登陆前点击的一个需要登录的页面,则在登录自动跳转到那个需要登录的页面。不跳转到此。
unauthorizedUrl:没有权限默认跳转的页面。
过滤器简称 |
对应的java类 |
anon |
org.apache.shiro.web.filter.authc.AnonymousFilter |
authc |
org.apache.shiro.web.filter.authc.FormAuthenticationFilter |
authcBasic |
org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter |
perms |
org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter |
port |
org.apache.shiro.web.filter.authz.PortFilter |
rest |
org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter |
roles |
org.apache.shiro.web.filter.authz.RolesAuthorizationFilter |
ssl |
org.apache.shiro.web.filter.authz.SslFilter |
user |
org.apache.shiro.web.filter.authc.UserFilter |
logout |
org.apache.shiro.web.filter.authc.LogoutFilter |
anon:例子/admins/**=anon 没有参数,表示可以匿名使用。
authc:例如/admins/user/**=authc表示需要认证(登录)才能使用,没有参数
roles:例子/admins/user/**=roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如admins/user/**=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。
perms:例子/admins/user/**=perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如/admins/user/**=perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
rest:例子/admins/user/**=rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。
port:例子/admins/user/**=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString
是你访问的url里的?后面的参数。
authcBasic:例如/admins/user/**=authcBasic没有参数表示httpBasic认证
ssl:例子/admins/user/**=ssl没有参数,表示安全的url请求,协议为https
user:例如/admins/user/**=user没有参数表示必须存在用户,当登入操作时不做检查
注:anon,authcBasic,auchc,user是认证过滤器,
perms,roles,ssl,rest,port是授权过滤器