常用的Realm有3种
- IniRealm:从ini文件加载安全数据
- JdbcRealm:从数据库加载安全数据
- 自定义Realm:开发中最常用
principal : 主体(Subject)的唯一标识,可以有多个principal,常见的比如用户名,手机号,邮箱等
credential:凭证, 一般是密码
如果有多个权限实体,权限可以写成 实体:权限 的形式,eg. video:watch,video:download,user:find,user:update
Shiro的依赖
shiro的依赖有很多,最常用的是 shiro-spring 。
IniRealm
1、resources下新建shiro.ini
文件名任意,因为后续要在代码中指定ini文件名
#用户信息
[users]
#用户名=密码,角色 一个用户可以对应多个角色,eg.同时是黄钻、绿钻,对应多个角色时,角色逗号分隔
chy1 = abcd, common
chy2 = abcd, vip
chy3 = abcd, svip
#角色信息
[roles]
#角色=权限 一个角色可以有多个权限,有多个权限时,权限逗号分隔。*表示具有全部权限
common = watch
vip = watch,download
svip = *
2、shiro工具类
/**
* Shiro工具类
*/
public class ShiroUtil {
//初始化
static {
//初始化IniRealm,指定shiro的ini配置文件
IniRealm iniRealm = new IniRealm("classpath:shiro.ini");
//配置SecurityManager
DefaultSecurityManager securityManager = new DefaultSecurityManager(iniRealm);
//设置SecurityUtils要使用的SecurityManager
SecurityUtils.setSecurityManager(securityManager);
}
//shiro的认证、授权操作都需要先将User包装为Subject,通过Subject来操作
/**
* 登录校验
* @return boolean 账户、密码是否匹配
*/
public static boolean login(String username,String password) {
Subject subject = SecurityUtils.getSubject();
//用token封装用户信息
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token); //将token与Realm中的安全数据对比。返回值是void,如果找不到匹配,直接抛出异常
} catch (AuthenticationException e) { //Realm中没有匹配的用户
return false;
}
return true;
}
/**
* 登出
*/
public static void logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
}
/**
* 检查用户是否拥有指定的角色
* @param role
* @return boolean 该用户是否具有指定的角色
*/
public static boolean hasRole(String role) {
Subject subject = SecurityUtils.getSubject();
return subject.hasRole(role);
}
/**
* 获取用户对应的角色
* @return String 用户对应的角色
*/
public static String getRole(){
Subject subject = SecurityUtils.getSubject();
String[] allRole = {"common", "vip", "svip"};
//如果用户同时具有多个角色,比如同时是绿钻、黄钻,可以放在数组、集合中返回
for (String role:allRole) {
if (subject.hasRole(role)) {
return role;
}
}
return null;
}
/**
* 检查用户是否拥有指定的权限
* @param permit 要检查的权限
* @return boolean 该用户是否具有指定的权限
*/
public static boolean isPermitted(String permit) {
Subject subject = SecurityUtils.getSubject();
return subject.isPermitted(permit);
}
}
导入类时,如果有同名的类,是导入shiro中的
JdbcRealm
安全数据一般存储在数据库中,一般都是从数据库中加载安全数据。
JdbcRealm中的部分源码
protected static final String DEFAULT_AUTHENTICATION_QUERY = "select password from users where username = ?";
protected static final String DEFAULT_SALTED_AUTHENTICATION_QUERY = "select password, password_salt from users where username = ?";
protected static final String DEFAULT_USER_ROLES_QUERY = "select role_name from user_roles where username = ?";
protected static final String DEFAULT_PERMISSIONS_QUERY = "select permission from roles_permissions where role_name = ?";
protected String authenticationQuery = "select password from users where username = ?";
protected String userRolesQuery = "select role_name from user_roles where username = ?";
protected String permissionsQuery = "select permission from roles_permissions where role_name = ?";
表名、字段名都是固定的
1、数据表
- 用户表users:需包含username、password、password_salt字段,用于指定用户名、密码
- 角色表user_roles:需包含username、role_name字段,用于指定用户对应的角色
- 权限表roles_permissions:需包含role_name、permission字段,用于指定角色对应的权限
以上字段均为varchar类型,其中password_salt字段的值可为null。3张表都可以新建id列作为主键,也可以直接使用现有的列作为主键(视业务而定)。
user_roles表通过username与users表关联,roles_permissions表通过role_name与user_roles表关联。
username唯一标识用户,不可重复。如果一个用户具有多种角色,写成多条记录的形式。如果一个角色具有多种权限,都写在permission字段中,英文逗号隔开,*表示具有全部权限。
2、shiro工具类
/**
* Shiro工具类
*/
public class ShiroUtil {
//初始化
static {
//初始化数据源
HikariDataSource dataSource=new HikariDataSource();
{
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/shiro?serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("abcd");
}
//初始化JdbcRealm
JdbcRealm jdbcRealm=new JdbcRealm();
jdbcRealm.setDataSource(dataSource);
jdbcRealm.setPermissionsLookupEnabled(true); //默认为false,需设置为true才能进行授权
//初始化SecurityManager
DefaultSecurityManager securityManager = new DefaultSecurityManager(jdbcRealm);
//设置SecurityUtils要使用的SecurityManager
SecurityUtils.setSecurityManager(securityManager);
}
//shiro的认证、授权操作都需要先将User包装为Subject,通过Subject来操作
/**
* 登录检查
* @return boolean 账号、密码是否匹配
*/
public static boolean login(String username, String password) {
Subject subject = SecurityUtils.getSubject();
//使用token封装用户信息
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
//将token与Realm中的信息进行对比。返回值是void,如果在Realm中找不到匹配,直接抛出异常
subject.login(token);
} catch (AuthenticationException e1) { //Realm中没有匹配的用户
return false;
}
return true;
}
/**
* 登出
*/
public static void logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
}
/**
* 检查用户是否拥有指定的角色
* @param role 角色
* @return boolean 该用户是否具有指定的角色
*/
public static boolean hasRole(String role) {
Subject subject = SecurityUtils.getSubject();
return subject.hasRole(role);
}
/**
* 获取用户对应的角色
* @return String 用户对应的角色
*/
public static String getRole(){
Subject subject = SecurityUtils.getSubject();
String[] allRole = {"common", "vip", "svip"};
//如果用户同时具有多个角色,比如同时是绿钻、黄钻,可以放在数组、集合中返回
for (String role:allRole){
if (subject.hasRole(role)){
return role;
}
}
return null;
}
/**
* 检查用户是否拥有指定的权限
* @param permit 要检查的权限
* @return boolean 该用户是否具有指定的权限
*/
public static boolean isPermitted(String permit) {
Subject subject = SecurityUtils.getSubject();
return subject.isPermitted(permit);
}
}
自定义Realm
IniRealm、JdbcRealm写死了校验规则,只能使用特定的表名,往往不符合需求,开发中一般使用自定义的Realm。
1、数据表设计
- tb_user:可以添加salt字段
- tb_role
- tb_permission:有多个需要验证权限的实体时,写成 实体:权限 或 实体_权限的形式,eg. video:watch 或 video_watch
- tb_user_role、tb_role_permission:此处是多对多,建立中间表;也可能是一对多,引入外键关联即可,具体视业务需求而定
2、新建类shiro.CustomRealm,继承AuthorizingRealm
/**
* 自定义的Realm
*/
public class CustomRealm extends AuthorizingRealm {
/**
* 授权方法,权限校验时自动调用
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获取主体标识
String username = (String) principalCollection.getPrimaryPrincipal();
//使用dao层,根据主体标识查询用户对应的角色、权限,此处略过
Set<String> permissions = null;
Set<String> roles = null;
//设置并返回主体的授权信息
SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
simpleAuthorizationInfo.setRoles(roles);
simpleAuthorizationInfo.setStringPermissions(permissions);
return simpleAuthorizationInfo;
}
/**
* 认证方法,登录时自动调用
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//获取主体标识
String username = (String) token.getPrincipal();
//使用dao层,根据主体标识查询用户正确的密码,此处略过
String pwd = null;
//如果账号、密码不匹配,返回null
if (pwd==null || pwd.equals("")){
return null;
}
//设置并返回主体的认证信息,主体标识、凭证、realm的名称
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username, pwd, this.getName());
return simpleAuthenticationInfo;
}
}
导入的类有多个候选项时,导入shiro中的
3、shiro工具类
/**
* Shiro工具类
*/
public class ShiroUtil {
//初始化
static {
//初始化Realm
CustomRealm customRealm = new CustomRealm();
//初始化SecurityManager
DefaultSecurityManager securityManager = new DefaultSecurityManager(customRealm);
//设置SecurityUtils要使用的SecurityManager
SecurityUtils.setSecurityManager(securityManager);
}
// shiro的认证、授权操作都需要先将User包装为Subject,通过Subject来操作
/**
* 登录校验
* @return boolean 账号、密码是否匹配
*/
public static boolean login(String username,String password) {
Subject subject = SecurityUtils.getSubject();
//使用token封装用户信息
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
//将token与Realm中的安全数据对比。返回值是void,如果在Realm中找不到匹配,直接抛出异常
subject.login(token);
} catch (AuthenticationException e1) { //Realm中没有匹配的用户
return false;
}
return true;
}
/**
* 登出
*/
public static void logout(){
Subject subject = SecurityUtils.getSubject();
subject.logout();
}
/**
* 检查用户是否拥有指定的角色
* @param role
* @return boolean 该用户是否拥有指定的角色
*/
public static boolean hasRole(String role) {
Subject subject = SecurityUtils.getSubject();
return subject.hasRole(role);
}
/**
* 获取用户对应的所有角色
* @return Set<String> 用户对应的所有角色
*/
public static Set<String> getRoles(){
Subject subject = SecurityUtils.getSubject();
Set<String> allRole = null; //系统所有的角色,此处略过
Set<String> roles = new HashSet<>(allRole.size()); //subject具有的所有角色
for (String role:allRole){
if (subject.hasRole(role)){
roles.add(role);
}
}
return roles;
}
/**
* 检查用户是否具有指定权限
* @param permit 要检查的权限
* @return boolean 该用户是否具有指定的权限
*/
public static boolean isPermitted(String permit) {
Subject subject = SecurityUtils.getSubject();
return subject.isPermitted(permit);
}
}
使用示例
if ( ShiroUtil.login("chy","abcd") ){
System.out.println("登录成功");
System.out.println("您的身份是:" + ShiroUtil.getRole());
System.out.println("是否具有download权限:" + ShiroUtil.isPermitted("download"));
}
else {
System.out.println("用户名或密码错误");
}