之前写过一个springboot整合shiro的文章, 那时候只是帮shiro跑起来, 可以实现拦截, 但是出问题了完全不知道怎么去解决, 这几天一直在反复使用shiro, 就想把他的运作原理搞懂; 正所谓知己知彼百战百胜嘛; 废话不多说, 直接干起来~~!
备注:文章末尾包含github的shiro_demo
1.shiro.jar
<!--shiro-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
2.shiro的配置类, ShiroConfig
当然ShiroConfig这个名字看心情取, 不过重要的是这个类上面要加个注解
@Configuration
用springboot的人应该都知道是啥, 告诉spring这个是个配置类, 跟读取xml文件一样, 在spring加载的时候容器创建对象的时候就要读取这个类; 也就是说这个类是在spring加载的时候读取的, 这个地方很重要, 后面的话我会因为这个类的读取时机而有一些小重点;
/**
* @Author MaoLG
* @Date 2018/11/17 11:24
*/
@Configuration
public class ShiroConfig {
/**
* ★必须 配置
* 该方法是读取自定义的认证授权规则
* 我这边实现的是 AuthorizingRealm 抽象类, 重写里面的
* doGetAuthorizationInfo(授权) ; doGetAuthenticationInfo(认证)方法
*/
@Bean
public ShiroRealm shiroRealm() {
ShiroRealm realm = new ShiroRealm();
return realm;
}
/**
* ★必须 配置
* SecurityManager 安全管理器,
*
* @return
*/
@Bean(name = "securityManager")
public DefaultWebSecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
//将自定义的realm加入该管理器中, 当使用shiro时, 安全管理器就会去找该规则去认证授权
securityManager.setRealm(shiroRealm());
return securityManager;
}
/**
* ★必须 配置
* 该方法为shiro的过滤器, 配置过滤的规则(哪些路径需要哪些权限,就在这里配置)
*/
@Bean(name = "shiroFilter")
public ShiroFilterFactoryBean shiroFilterFactoryBean() throws DataAccessException {
ShiroFilterFactoryBean shiroFilterFactoryBean = null;
shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager());
//url权限配置, url为controller设置的可访问的路径 ps:url可以使用*(通配符设置)
Map<String, String> filterChainDefinitionManager = new LinkedHashMap<String, String>();
//可以匿名访问的权限 anon:不登录也可访问
filterChainDefinitionManager.put("/user/login", "anon");
//必须登录的权限 authc:必须登录才能访问的权限, 配置了访问权限的可以不用再配置必须登录访问
filterChainDefinitionManager.put("/user/add", "authc");
filterChainDefinitionManager.put("/user/update", "authc");
//退出登录 logout:退出登录, shiro帮我们实现, 该路径方法可不编写任何逻辑,可清楚shiro记录的认证
filterChainDefinitionManager.put("/user/logout", "logout");
//动态获取, 设置权限访问
/*★这里就是上面说的小重点, 因为配置类是在spring创建对象的时候读取的, 我的权限是配置
在数据库中(这是废话本来就要配置在数据库中)
用mybatis来访问数据库,是不可能的(此时spring中的对象还没有全部放在容器中, @Autowired注入不了值) 这里用的是自己封装的JDBC直接访问的数据库
*/
JDBCTemplate jdbcTemplate = new JDBCTemplate();
String permissionSql = "select name , url from t_permission";
List<Permission> permissions = jdbcTemplate.query(permissionSql, new PermissionMapper(), null);
for (Permission permission: permissions){
//当配置了perms参数的字符串就会调用授权方法, 没配置不会调用
filterChainDefinitionManager.put(permission.getUrl(), "perms["+permission.getName()+"]");
}
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionManager);
//配置当未登录时候跳转的路径
shiroFilterFactoryBean.setLoginUrl("/login.html");
//配置没有权限时候访问的路径
shiroFilterFactoryBean.setUnauthorizedUrl("/401.html");
return shiroFilterFactoryBean;
}
}
3.自定义规则 ShiroRealm
/**
* 自定义realm
*
* @Author MaoLG
* @Date 2018/11/17 13:03
*/
public class ShiroRealm extends AuthorizingRealm {
//读取用户权限,我这里没有角色表, 具体根据自己实际需求编写
@Autowired
private PermissionDao permissionDao;
@Autowired
private UserDao userDao;
@Autowired
private RoleDao roleDao;
/**
* 授权
* 该方法将用户拥有的权限和角色放入到SimpleAuthorizationInfo 对象中, 由shiro来进行权限校验
* 访问每一个权限接口时候 都会调一次该方法, 也就意味着每次都会访问数据库, 这里需要调用一次该方法的时候将权限/角色缓存起来,
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
//获得登录时提供的重要凭证, 这里获得的是登录账号
String username = (String) principalCollection.getPrimaryPrincipal();
//查询到该账号下所有的权限d
List<Permission> permissions = permissionDao.selectUserOwnPermission(username);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//把用户所有权限加在SimpleAuthorizationInfo 对象中, shiro自己来读取对比权限
for (Permission permission : permissions) {
info.addStringPermission(permission.getName());
}
//如果有角色的需要, 调用info.addRole("角色"); 同权限一个道理
//添加角色
List<Role> roles = roleDao.selectUserOwnRole(username);
for (Role role: roles){
info.addRole(role.getName());
}
System.out.println("授权");
return info;
}
/**
* 认证
* .....这个太简单了不知道怎么去描述了
* 想到了, 当用户登录, 也就是调用ubject subject = SecurityUtils.getSubject();
* subject.login(token); 这个的时候就调用该方法,
* @param authenticationToken
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
throws AuthenticationException {
SimpleAuthenticationInfo info = null;
try {
//强转UsernamePasswordToken 类型
UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
//获得登录账号
String username = token.getUsername();
char[] pwd = token.getPassword();
//获得登密码
String password = String.valueOf(pwd);
User user = new User();
user.setUsername(username);
user.setPassword(password);
QueryWrapper<User> queryWrapper = new QueryWrapper<>(user);
User u = userDao.selectOne(queryWrapper);
if (u == null) {
//账号不存在抛出该异常, controller抓取丢到前台就好了
throw new AuthenticationException("账号或密码错误");
} else {
//shiro默认配置回去找t_user表 来查询 , 也可根据自己实际业务编写
info = new SimpleAuthenticationInfo(username, password, getName());
System.out.println("认证");
}
} catch (AuthenticationException e) {
throw e;
}
return info;
}
}
配置完着俩类, 你的shiro就可以用了, 然后我有一点疑惑, shiro的过滤器方法也提供了roles[角色名] 的过滤参数, 让我搞不太明白 我觉得角色有必要再shiro里面配置吗? 因为有role下面包含的是权限,权限已经配置拦截, 再配置role拦截好像多次一举 例如:
filterChainDefinitionManager.put("/user/update", "roles[admin]");
filterChainDefinitionManager.put("/user/update", "perms[update]");
个人觉得, role只是为了便于管理权限, 而不作为拦截的标志; 如果有不同意见 欢迎留言, 最后把我自己的写的shiro的小demo贴下;
shiro_demo,实现登录认证访问授权,内涵sql文件
2018/11/29更新-自动登录rememberMe
更新-ShiroRealm自定义缓存
网友评论