什么是shiro???
Apache Shiro是一个强大且易用的Java安全框架,执行身份验证、授权、密码和会话管理。使用Shiro的易于理解的API,您可以快速、轻松地获得任何应用程序,从最小的移动应用程序到最大的网络和企业应用程序。
主要功能
三个核心组件:Subject, SecurityManager 和 Realms.
Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
Subject代表了当前用户的安全操作,SecurityManager则管理所有用户的安全操作。
SecurityManager:它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
Realm: Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
Shiro内置了可以连接大量安全数据源(又名目录)的Realm,如LDAP、关系数据库(JDBC)、类似INI的文本配置资源以及属性文件等。如果缺省的Realm不能满足需求,你还可以插入代表自定义数据源的自己的Realm实现。
好了,现在大致了解了什么是shiro,现在我进一步取了解
1:Subject:即“当前操作用户”。但是,在Shiro中,Subject这一概念并不仅仅指人,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。它仅仅意味着“当前跟软件交互的东西”。
上源码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
public interface Subject {
Object getPrincipal();
PrincipalCollection getPrincipals();
boolean isPermitted(String var1);
boolean isPermitted(Permission var1);
boolean[] isPermitted(String... var1);
boolean[] isPermitted(List<Permission> var1);
boolean isPermittedAll(String... var1);
boolean isPermittedAll(Collection<Permission> var1);
void checkPermission(String var1) throws AuthorizationException;
void checkPermission(Permission var1) throws AuthorizationException;
void checkPermissions(String... var1) throws AuthorizationException;
void checkPermissions(Collection<Permission> var1) throws AuthorizationException;
boolean hasRole(String var1);
boolean[] hasRoles(List<String> var1);
boolean hasAllRoles(Collection<String> var1);
void checkRole(String var1) throws AuthorizationException;
void checkRoles(Collection<String> var1) throws AuthorizationException;
void checkRoles(String... var1) throws AuthorizationException;
void login(AuthenticationToken var1) throws AuthenticationException;
boolean isAuthenticated();
boolean isRemembered();
Session getSession();
Session getSession(boolean var1);
void logout();
<V> V execute(Callable<V> var1) throws ExecutionException;
void execute(Runnable var1);
<V> Callable<V> associateWith(Callable<V> var1);
Runnable associateWith(Runnable var1);
void runAs(PrincipalCollection var1) throws NullPointerException, IllegalStateException;
boolean isRunAs();
PrincipalCollection getPreviousPrincipals();
PrincipalCollection releaseRunAs();
public static class Builder {
private final SubjectContext subjectContext;
private final SecurityManager securityManager;
public Builder() {
this(SecurityUtils.getSecurityManager());
}
public Builder(SecurityManager securityManager) {
if (securityManager == null) {
throw new NullPointerException("SecurityManager method argument cannot be null.");
} else {
this.securityManager = securityManager;
this.subjectContext = this.newSubjectContextInstance();
if (this.subjectContext == null) {
throw new IllegalStateException("Subject instance returned from 'newSubjectContextInstance' cannot be null.");
} else {
this.subjectContext.setSecurityManager(securityManager);
}
}
}
protected SubjectContext newSubjectContextInstance() {
return new DefaultSubjectContext();
}
protected SubjectContext getSubjectContext() {
return this.subjectContext;
}
public Subject.Builder sessionId(Serializable sessionId) {
if (sessionId != null) {
this.subjectContext.setSessionId(sessionId);
}
return this;
}
public Subject.Builder host(String host) {
if (StringUtils.hasText(host)) {
this.subjectContext.setHost(host);
}
return this;
}
public Subject.Builder session(Session session) {
if (session != null) {
this.subjectContext.setSession(session);
}
return this;
}
public Subject.Builder principals(PrincipalCollection principals) {
if (principals != null && !principals.isEmpty()) {
this.subjectContext.setPrincipals(principals);
}
return this;
}
public Subject.Builder sessionCreationEnabled(boolean enabled) {
this.subjectContext.setSessionCreationEnabled(enabled);
return this;
}
public Subject.Builder authenticated(boolean authenticated) {
this.subjectContext.setAuthenticated(authenticated);
return this;
}
public Subject.Builder contextAttribute(String attributeKey, Object attributeValue) {
if (attributeKey == null) {
String msg = "Subject context map key cannot be null.";
throw new IllegalArgumentException(msg);
} else {
if (attributeValue == null) {
this.subjectContext.remove(attributeKey);
} else {
this.subjectContext.put(attributeKey, attributeValue);
}
return this;
}
}
public Subject buildSubject() {
return this.securityManager.createSubject(this.subjectContext);
}
}
}
这里面包含了“当前用户”的所有信息,包括role,session,Permission,
上工具类 ShiroUtils
/**
* shiro 工具类
*
* @author lp
*/
@Component
public class ShiroUtils {
private static final SysUserConfigService userService= SpringUtils.getBean(SysUserConfigService.class);
private ShiroUtils(){}
/**
* 获取shiro subject
* @return
* @author lp
* @Date 2019年11月21日 上午10:00:55
*/
public static Subject getSubjct()
{
return SecurityUtils.getSubject();
}
/**
* 获取登录session
* @return
* @author lp
* @Date 2019年11月21日 上午10:00:41
*/
public static Session getSession()
{
return SecurityUtils.getSubject().getSession();
}
/**
* 退出登录
* @author lp
* @Date 2019年11月21日 上午10:00:24
*/
public static void logout()
{
getSubjct().logout();
}
/**
* 获取登录用户model
* @return
* @author lp
* @Date 2019年11月21日 上午10:00:10
*/
public static TsysUser getUser()
{
TsysUser user = null;
Object obj = getSubjct().getPrincipal();
if (StringUtils.isNotNull(obj))
{
user = new TsysUser();
BeanUtils.copyBeanProp(user, obj);
}
if(user ==null ) return null;
//添加用户会员信息
user.setSysUserConfig(userService.selectByUserId(user.getId()));
return user;
}
/**
* set用户
* @param user
* @author lp
* @Date 2019年11月21日 上午9:59:52
*/
public static void setUser(TsysUser user)
{
Subject subject = getSubjct();
PrincipalCollection principalCollection = subject.getPrincipals();
String realmName = principalCollection.getRealmNames().iterator().next();
PrincipalCollection newPrincipalCollection = new SimplePrincipalCollection(user, realmName);
// 重新加载Principal
subject.runAs(newPrincipalCollection);
}
/**
* 清除授权信息
* @author lp
* @Date 2019年11月21日 上午9:59:37
*/
public static void clearCachedAuthorizationInfo()
{
RealmSecurityManager rsm = (RealmSecurityManager) SecurityUtils.getSecurityManager();
MyShiroRealm realm = (MyShiroRealm) rsm.getRealms().iterator().next();
realm.clearCachedAuthorizationInfo();
}
/**
* 获取登录用户id
* @return
* @author lp
* @Date 2019年11月21日 上午9:58:55
*/
public static String getUserId()
{
TsysUser tsysUser = getUser();
if (tsysUser == null || tsysUser.getId() == null){
throw new RuntimeException("用户不存在!");
}
return tsysUser.getId().trim();
}
/**
* 获取登录用户name
* @return
* @author lp
* @Date 2019年11月21日 上午9:58:48
*/
public static String getLoginName()
{
TsysUser tsysUser = getUser();
if (tsysUser == null){
throw new RuntimeException("用户不存在!");
}
return tsysUser.getUsername();
}
/**
* 获取登录用户ip
* @return
* @author lp
* @Date 2019年11月21日 上午9:58:26
*/
public static String getIp()
{
return getSubjct().getSession().getHost();
}
/**
* 获取登录用户sessionid
* @return
* @author lp
* @Date 2019年11月21日 上午9:58:37
*/
public static String getSessionId()
{
return String.valueOf(getSubjct().getSession().getId());
}
}
这个工具类基本包含了正常开发使用所需要的东西
注意这段代码,在Subject中获取用户信息
private static final SysUserConfigService userService= SpringUtils.getBean(SysUserConfigService.class); //如何再非spring环境使用spring,这样我们就可以使用spring 中定义的service
/**
* 获取登录用户model
* @return
* @author lp
* @Date 2019年11月21日 上午10:00:10
*/
public static TsysUser getUser()
{
TsysUser user = null;
Object obj = getSubjct().getPrincipal();
if (StringUtils.isNotNull(obj))
{
user = new TsysUser();
BeanUtils.copyBeanProp(user, obj);
}
if(user ==null ) return null;
//添加用户会员信息
user.setSysUserConfig(userService.selectByUserId(user.getId()));
//在返回model 中添加用户配置信息,也可以在Realm 中添加
return user;
//系统中获取当前登录用户,直接shiroUtiles.getUser()
}
SecurityManager 它是Shiro框架的核心,典型的Facade模式,Shiro通过SecurityManager来管理内部组件实例,并通过它来提供安全管理的各种服务。
public interface SecurityManager extends Authenticator, Authorizer, SessionManager {
//实现了 Authenticator类,Authorizer,SessionManager SecurityManager 的核心所在
Subject login(Subject var1, AuthenticationToken var2) throws AuthenticationException;
void logout(Subject var1);
Subject createSubject(SubjectContext var1);
}
Realm Realm充当了Shiro与应用安全数据间的“桥梁”或者“连接器”。也就是说,当对用户执行认证(登录)和授权(访问控制)验证时,Shiro会从应用配置的Realm中查找用户及其权限信息。
从这个意义上讲,Realm实质上是一个安全相关的DAO:它封装了数据源的连接细节,并在需要时将相关数据提供给Shiro。当配置Shiro时,你必须至少指定一个Realm,用于认证和(或)授权。配置多个Realm是可以的,但是至少需要一个。
如何封装合适自己的Realm,上代码,教大家配置一个属于自己的Realm
MyShiroRealm
/**
* 身份校验核心类
*
* @ClassName: MyShiroRealm
* @author lp
* @date 2019年8月25日
*
*/
@Service
public class MyShiroRealm extends AuthorizingRealm {
@Autowired
private TsysUserDao tsysUserDao;
@Autowired
private PermissionDao permissionDao;//权限dao
@Autowired
private RoleDao roleDao ;//角色dao
/**
* 认证登陆
*/
@SuppressWarnings("unused")
@Override
protected AuthenticationInfo doGetAuthenticationInfo(
AuthenticationToken token) throws AuthenticationException {
//加这一步的目的是在Post请求的时候会先进认证,然后在到请求
if (token.getPrincipal() == null) {
return null;
}
String username = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
// 通过username从数据库中查找 User对象,如果找到,没找到.
// 实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
TsysUser userInfo = tsysUserDao.queryUserName(username);
if (userInfo == null)
return null;
else{
SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
userInfo, // 用户名
userInfo.getPassword(), // 密码
getName() // realm name
);
return authenticationInfo;
}
}
/**
* 授权查询回调函数, 进行鉴权但缓存中无用户的授权信息时调用.
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
//System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()");
if(principals == null){
throw new AuthorizationException("principals should not be null");
}
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
TsysUser userinfo = (TsysUser)principals.getPrimaryPrincipal();
String uid=userinfo.getId();
List<TsysRole> tsysRoles= roleDao.queryUserRole(uid);
for(TsysRole userrole:tsysRoles){
//System.out.println("角色名字:"+gson.toJson(userrole));
String rolid=userrole.getId();//角色id
authorizationInfo.addRole(userrole.getName());//添加角色名字
List<TsysPermission> permissions=permissionDao.queryRoleId(rolid);
for(TsysPermission p:permissions){
//System.out.println("角色下面的权限:"+gson.toJson(p));
if(StringUtils.isNotEmpty(p.getPerms())){
authorizationInfo.addStringPermission(p.getPerms());
}
}
}
return authorizationInfo;
}
/**
* 清理缓存权限
*/
public void clearCachedAuthorizationInfo()
{
this.clearCachedAuthorizationInfo(SecurityUtils.getSubject().getPrincipals());
}
}
以上便是shiro的精华,当然还需要各种配置才能是shrio在项目中“活”起来
配置一个属于自己的shiroConfig
/**
* 权限配置文件
* @ClassName: ShiroConfiguration
* @author lp
* @date 2019年8月25日
*
*/
@Configuration
public class ShiroConfig {
/**
* 这是shiro的大管家,相当于mybatis里的SqlSessionFactoryBean
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(org.apache.shiro.mgt.SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// Map filtersMap = new LinkedHashMap();
// //自定义拦截器
// filtersMap.put("authc", new ShiroLoginFilter());
// shiroFilterFactoryBean.setFilters(filtersMap);
shiroFilterFactoryBean.setFilterChainDefinitionMap(ShiroFilterMapFactory.shiroFilterMap());
//登录
shiroFilterFactoryBean.setLoginUrl("/admin/login");
//首页
shiroFilterFactoryBean.setSuccessUrl("/admin/login");
shiroFilterFactoryBean.setSuccessUrl("/admin/index");
// //错误页面,认证不通过跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/error/403");
// //页面权限控制
shiroFilterFactoryBean.setSecurityManager(securityManager);
return shiroFilterFactoryBean;
}
/**
* web应用管理配置
* @param shiroRealm
* @param cacheManager
* @param manager
* @return
*/
@Bean
public DefaultWebSecurityManager securityManager(Realm shiroRealm,CacheManager cacheManager,RememberMeManager manager) {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setCacheManager(cacheManager);
securityManager.setRememberMeManager(manager);//记住Cookie
securityManager.setRealm(shiroRealm);
securityManager.setSessionManager(sessionManager());
return securityManager;
}
/**
* session过期控制
* @return
* @author lp
* @Date 2019年11月2日 下午12:49:49
*/
@Bean
public DefaultWebSessionManager sessionManager() {
DefaultWebSessionManager defaultWebSessionManager=new DefaultWebSessionManager();
// 设置session过期时间3600s
Long timeout=60L*1000*60;//毫秒级别
defaultWebSessionManager.setGlobalSessionTimeout(timeout);
return defaultWebSessionManager;
}
/**
* 加密算法
* @return
*/
@Bean
public HashedCredentialsMatcher hashedCredentialsMatcher() {
HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
hashedCredentialsMatcher.setHashAlgorithmName("MD5");//采用MD5 进行加密
hashedCredentialsMatcher.setHashIterations(1);//加密次数
return hashedCredentialsMatcher;
}
/**
* 记住我的配置
* @return
*/
@Bean
public RememberMeManager rememberMeManager() {
Cookie cookie = new SimpleCookie("rememberMe");
cookie.setHttpOnly(true);//通过js脚本将无法读取到cookie信息
cookie.setMaxAge(60 * 60 * 24);//cookie保存一天
CookieRememberMeManager manager=new CookieRememberMeManager();
manager.setCookie(cookie);
return manager;
}
/**
* 缓存配置
* @return
*/
@Bean
public CacheManager cacheManager() {
MemoryConstrainedCacheManager cacheManager=new MemoryConstrainedCacheManager();//使用内存缓存
return cacheManager;
}
/**
* 配置realm,用于认证和授权
* @param hashedCredentialsMatcher
* @return
*/
@Bean
public AuthorizingRealm shiroRealm(HashedCredentialsMatcher hashedCredentialsMatcher) {
MyShiroRealm shiroRealm = new MyShiroRealm();
//校验密码用到的算法
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return shiroRealm;
}
/**
* 启用shiro方言,这样能在页面上使用shiro标签
* @return
*/
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
/**
* 启用shiro注解
*加入注解的使用,不加入这个注解不生效
*/
@Bean
public AuthorizationAttributeSourceAdvisor getAuthorizationAttributeSourceAdvisor(org.apache.shiro.mgt.SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
advisor.setSecurityManager(securityManager);
return advisor;
}
}
shiroFilterMap 封装shiro内部拦截顺序
/**
* @ClassName: ShiroFilterMapFactory
* @author lp
* @date 2019年8月26日
*
*/
public class ShiroFilterMapFactory {
/**
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没有参数表示必须存在用户,当登入操作时不做检查
*/
public static Map<String, String> shiroFilterMap() {
// 设置路径映射,注意这里要用LinkedHashMap 保证有序
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/Api/**/**", "anon");
//对所有用户认证
filterChainDefinitionMap.put("/user/loginByUser", "anon"); //用户登录
filterChainDefinitionMap.put("/user/loginByPhone", "anon");//手机号登录
filterChainDefinitionMap.put("/user/getPhoneCode", "anon");//获取手机验证码
filterChainDefinitionMap.put("/user/registered", "anon");//注册
filterChainDefinitionMap.put("/user/forgetPassword", "anon");//忘记密码
//swagger放开过滤
filterChainDefinitionMap.put("/swagger-ui.html", "anon");
filterChainDefinitionMap.put("/webjars/**", "anon");
filterChainDefinitionMap.put("/v2/**", "anon");
filterChainDefinitionMap.put("/swagger-resources/**", "anon");
filterChainDefinitionMap.put("/static/**", "anon");//静态文件
filterChainDefinitionMap.put("/admin/login", "anon");//登录页面
filterChainDefinitionMap.put("/admin/logout", "logout");//登出页面
//放验证码
filterChainDefinitionMap.put("/captcha/**", "anon");//验证码
// 释放 druid 监控画面
filterChainDefinitionMap.put("/druid/**", "anon");
//释放websocket请求
filterChainDefinitionMap.put("/websocket", "anon");
//前端
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/index", "anon");//任务调度暂时放开
filterChainDefinitionMap.put("/quartz/**", "anon");
//
//对所有页面进行认证
filterChainDefinitionMap.put("/**","authc");
return filterChainDefinitionMap;
}
}
配置结束,但是如何在项目中使用呢 ? 接下来模拟一个用户登录场景,我们从shiro验证用户权限,和信息 用户登录接口
String userName = user.getUsername();
Subject currentUser = SecurityUtils.getSubject();
//是否验证通过
if(!currentUser.isAuthenticated()) {
UsernamePasswordToken token =new UsernamePasswordToken(userName,user.getPassword());
try {
if(rememberMe) {
token.setRememberMe(true);
}
//存入用户
currentUser.login(token);
if(StringUtils.isNotNull(ShiroUtils.getUser())) {
return AjaxResult.success();
}else {
return AjaxResult.error(500,"未知账户");
}
}catch (UnknownAccountException uae) {
logger.info("对用户[" + userName + "]进行登录验证..验证未通过,未知账户");
return AjaxResult.error(500,"未知账户");
}
shrio 会根据用户名,密码跳到我们的Realm里面验证
/**
* 配置realm,用于认证和授权
* @param hashedCredentialsMatcher
* @return
*/
@Bean
public AuthorizingRealm shiroRealm(HashedCredentialsMatcher hashedCredentialsMatcher) {
MyShiroRealm shiroRealm = new MyShiroRealm();
//校验密码用到的算法
shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher);
return shiroRealm;
}
验证成功后,读取我们在ShiroConfig配置的setSuccessUrl
//登录
shiroFilterFactoryBean.setLoginUrl("/admin/login");
//首页
shiroFilterFactoryBean.setSuccessUrl("/admin/login");
shiroFilterFactoryBean.setSuccessUrl("/admin/index");
// //错误页面,认证不通过跳转
shiroFilterFactoryBean.setUnauthorizedUrl("/error/403");