SpringBoot集成Shiro思路以及代码过程

模型 
假设存在用户(User)、角色(Role)、权限(Permission)3中模型。依赖关系如下:
用户分配角色,角色分配权限。用户不直接参与权限的分配。
SpringBoot集成Shiro思路以及代码过程

SpringBoot集成Shiro流程 
demo的代码架构如下:

SpringBoot集成Shiro思路以及代码过程


创建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配置存在如下关系

SpringBoot集成Shiro思路以及代码过程


3、mapper.xml
mapper中配置好各个IO的对应情况

SpringBoot集成Shiro思路以及代码过程


需要配置读取UserMapper.xml的路径

SpringBoot集成Shiro思路以及代码过程


application中需要配置需要加载的mapper类路径

SpringBoot集成Shiro思路以及代码过程


授权与鉴权
需要继承 import org.apache.shiro.realm.AuthorizingRealm; 实现当中的方法。

SpringBoot集成Shiro思路以及代码过程


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");

// loginloginUser表单不需要验证
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作缓存只能用于本机,那么在集群时就无法使用,
* 如果使用ehcacheredis等其他缓存,可以参考https://www.cnblogs.com/lic309/p/4072848.html
*/
authRealm.setCacheManager(new MemoryConstrainedCacheManager());

// com.mmall.demo2.CredentialMatcher中自定义的密码比较器对密码进行比较
authRealm.setCredentialsMatcher(new CredentialMatcher());
return authRealm;
}


// shirospring进行绑定
@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的方法解释:

  1. /** 
  2.  * Shiro-1.2.2内置的FilterChain 
  3.  * @see ============================================================================================================================= 
  4.  * @see 1)Shiro验证URL时,URL匹配成功便不再继续匹配查找(所以要注意配置文件中的URL顺序,尤其在使用通配符时) 
  5.  * @see   故filterChainDefinitions的配置顺序为自上而下,以最上面的为准 
  6.  * @see 2)当运行一个Web应用程序时,Shiro将会创建一些有用的默认Filter实例,并自动地在[main]项中将它们置为可用 
  7.  * @see   自动地可用的默认的Filter实例是被DefaultFilter枚举类定义的,枚举的名称字段就是可供配置的名称 
  8.  * @see   anon---------------org.apache.shiro.web.filter.authc.AnonymousFilter 
  9.  * @see   authc--------------org.apache.shiro.web.filter.authc.FormAuthenticationFilter 
  10.  * @see   authcBasic---------org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter 
  11.  * @see   logout-------------org.apache.shiro.web.filter.authc.LogoutFilter 
  12.  * @see   noSessionCreation--org.apache.shiro.web.filter.session.NoSessionCreationFilter 
  13.  * @see   perms--------------org.apache.shiro.web.filter.authz.PermissionAuthorizationFilter 
  14.  * @see   port---------------org.apache.shiro.web.filter.authz.PortFilter 
  15.  * @see   rest---------------org.apache.shiro.web.filter.authz.HttpMethodPermissionFilter 
  16.  * @see   roles--------------org.apache.shiro.web.filter.authz.RolesAuthorizationFilter 
  17.  * @see   ssl----------------org.apache.shiro.web.filter.authz.SslFilter 
  18.  * @see   user---------------org.apache.shiro.web.filter.authz.UserFilter 
  19.  * @see ============================================================================================================================= 
  20.  * @see 3)通常可将这些过滤器分为两组 
  21.  * @see   anon,authc,authcBasic,user是第一组认证过滤器 
  22.  * @see   perms,port,rest,roles,ssl是第二组授权过滤器 
  23.  * @see   注意user和authc不同:当应用开启了rememberMe时,用户下次访问时可以是一个user,但绝不会是authc,因为authc是需要重新认证的 
  24.  * @see                      user表示用户不一定已通过认证,只要曾被Shiro记住过登录状态的用户就可以正常发起请求,比如rememberMe 
  25.  * @see                      说白了,以前的一个用户登录时开启了rememberMe,然后他关闭浏览器,下次再访问时他就是一个user,而不会authc 
  26.  * @see ============================================================================================================================= 
  27.  * @see 4)举几个例子 
  28.  * @see   /admin=authc,roles[admin]      表示用户必需已通过认证,并拥有admin角色才可以正常发起'/admin'请求 
  29.  * @see   /edit=authc,perms[admin:edit]  表示用户必需已通过认证,并拥有admin:edit权限才可以正常发起'/edit'请求 
  30.  * @see   /home=user                     表示用户不一定需要已经通过认证,只需要曾经被Shiro记住过登录状态就可以正常发起'/home'请求 
  31.  * @see ============================================================================================================================= 
  32.  * @see 5)各默认过滤器常用如***意URL Pattern里用到的是两颗星,这样才能实现任意层次的全匹配) 
  33.  * @see   /admins/**=anon             无参,表示可匿名使用,可以理解为匿名用户或游客 
  34.  * @see   /admins/user/**=authc       无参,表示需认证才能使用 
  35.  * @see   /admins/user/**=authcBasic  无参,表示httpBasic认证 
  36.  * @see   /admins/user/**=user        无参,表示必须存在用户,当登入操作时不做检查 
  37.  * @see   /admins/user/**=ssl         无参,表示安全的URL请求,协议为https 
  38.  * @see   /admins/user/**=perms[user:add:*] 
  39.  * @see       参数可写多个,多参时必须加上引号,且参数之间用逗号分割,如/admins/user/**=perms["user:add:*,user:modify:*"] 
  40.  * @see       当有多个参数时必须每个参数都通过才算通过,相当于isPermitedAll()方法 
  41.  * @see   /admins/user/**=port[8081] 
  42.  * @see       当请求的URL端口不是8081时,跳转到schemal://serverName:8081?queryString 
  43.  * @see       其中schmal是协议http或https等,serverName是你访问的Host,8081是Port端口,queryString是你访问的URL里的?后面的参数 
  44.  * @see   /admins/user/**=rest[user] 
  45.  * @see       根据请求的方法,相当于/admins/user/**=perms[user:method],其中method为post,get,delete等 
  46.  * @see   /admins/user/**=roles[admin] 
  47.  * @see       参数可写多个,多个时必须加上引号,且参数之间用逗号分割,如/admins/user/**=roles["admin,guest"] 
  48.  * @see       当有多个参数时必须每个参数都通过才算通过,相当于hasAllRoles()方法 
  49.  * @see ============================================================================================================================= 
  50.  * @create Sep 29, 2013 5:24:49 PM 
  51.  * @author 玄玉<http://blog.csdn.net/jadyer> 
  52.  */  


我截取一段关于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是授权过滤器