shiro使用
介绍
简介
- Apache Shiro 是一个Java 的安全(权限)框架。
- Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环 境。
- Shiro可以完成,认证,授权,加密,会话管理,Web集成,缓存等
关键对象
- Subject:任何可以与应用交互的 ‘用户’;
- Security Manager:相当于SpringMVC中的DispatcherServlet;是Shiro的心脏,所有具体的交互 都通过Security Manager进行控制,它管理者所有的Subject,且负责进行认证,授权,会话,及 缓存的管理。
- Authenticator:负责Subject认证,是一个扩展点,可以自定义实现;可以使用认证策略 (Authentication Strategy),即什么情况下算用户认证通过了;
- Authorizer:授权器,即访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能 访问应用中的那些功能;
- Realm:可以有一个或者多个的realm,可以认为是安全实体数据源,即用于获取安全实体的,可 以用JDBC实现,也可以是内存实现等等,由用户提供;所以一般在应用中都需要实现自己的realm
- SessionManager:管理Session生命周期的组件,而Shiro并不仅仅可以用在Web环境,也可以用 在普通的JavaSE环境
- CacheManager:缓存控制器,来管理如用户,角色,权限等缓存的;因为这些数据基本上很少改 变,放到缓存中后可以提高访问的性能;
- Cryptography:密码模块,Shiro 提高了一些常见的加密组件用于密码加密,解密等
实际运用
Springboot 整合使用shiro
引入相关依赖
相关依赖如下
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.3.2</version>
</dependency>
配置shiro
shiro 主要需要配置一个过滤器 ShiroFilterFactoryBean,我们需要为ShiroFilterFactoryBean 配置拦截路径,安全管理器securityManager等
@Configuration
public class ShiroConfig {
/**
* ShiroFilterFactoryBean 处理拦截资源文件问题。
* 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager
* Filter Chain定义说明 1、一个URL可以配置多个Filter,使用逗号分隔 2、当设置多个过滤器时,全部验证通过,才视为通过
* 3、部分过滤器可指定参数,如perms,roles
*/
@Bean
public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
// 必须设置 SecurityManager
shiroFilterFactoryBean.setSecurityManager(securityManager);
// 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
//访问的是后端url地址为 /login的接口
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接
shiroFilterFactoryBean.setSuccessUrl("/index");
// 未授权界面;
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
// 拦截器.
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/ajaxLogin", "anon");
filterChainDefinitionMap.put("/userlogin", "anon");
// 配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
filterChainDefinitionMap.put("/logout", "logout");
//配置某个url需要某个权限码
filterChainDefinitionMap.put("/hello", "perms[how_are_you]");
// 过滤链定义,从上向下顺序执行,一般将 /**放在最为下边
// <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
filterChainDefinitionMap.put("/", "user");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
System.out.println("Shiro拦截器工厂类注入成功");
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
// 设置realm.
securityManager.setRealm(myShiroRealm());
}
/**
* 身份认证realm; (这个需要自己写,账号密码校验;权限等)
* @return
*/
@Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm myShiroRealm = new MyShiroRealm();
return myShiroRealm;
}
}
自定义ShiroRealm
继承AuthorizingRealm抽象类 主要实现两个方法 认证与授权
- 认证
获取登录用户名username 使用username与数据库进行匹配 查询不到数据抛出UnknownAccountException 异常
如果查询到数据则通过SimpleAuthenticationInfo(Object principal, Object credentials, String realmName)进行认证 - 授权(并非每次调用接口都会执行授权方法 调用需要操作权限的接口才会执行认证方法)
通过SecurityUtils.getSubject() 获取subject对象
通过subject获取当前登录用户
数据库查询用户所拥有的权限 并添加权限信息simpleAuthorInfo.addStringPermission(“user:add”);
public class MyRealm extends AuthorizingRealm{
/*授权*/
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("执行了授权=>AuthorizationInfo");
/*给当前用户授权*/
SimpleAuthorizationInfo simpleAuthorInfo = new SimpleAuthorizationInfo();
/*根据用户去数据库查询权限*/
Subject subject = SecurityUtils.getSubject();
User user = (User)subject.getPrincipal();
System.out.println(user.toString());
simpleAuthorInfo.addStringPermission("user:add");//给当前用户授权url为hello的权限码
System.out.println("经试验:并不是每次调用接口就会执行,而是调用需要操作码(permission)的接口就会执行");
return simpleAuthorInfo;
}
/*认证*/
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
System.out.println("执行了认证=>AuthenticationInfo");
UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
String username = usernamePasswordToken.getUsername();
String password = new String(usernamePasswordToken.getPassword());
/*System.out.println("-------" + usernamePasswordToken.getHost().toString()
+ usernamePasswordToken.getUsername() + usernamePasswordToken.getCredentials().toString()
+ usernamePasswordToken.getPrincipal() + usernamePasswordToken.getPassword().toString());*/
/*从数据库中获取用户账号密码*/
User user = new User("1","root","123456","2");
// char[] password = usernamePasswordToken.getPassword();
/*根据账号去数据库中查找记录*/
/*为空返回账号不存在错误*/
if(!username.equals("root")){
throw new UnknownAccountException("账号不存在");
}
/*不为空 验证密码*/
return new SimpleAuthenticationInfo(user,user.getPassword(),"");
}
}
登录退出
- 登录
登录时首先封装用户的账号密码信息到UsernamePasswordToken,然后调用subject.login(UsernamePasswordToken) 无异常则登录成功
public String login(String username,String password,Model model){
//获取当前的用户
Subject subject = SecurityUtils.getSubject();
//封装用户的登录数据
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
try {
subject.login(token);//执行登录方法,如果没有异常就说明OK
return "index";
} catch (UnknownAccountException e){//用户名不存在
model.addAttribute("msg","用户名错误");
return "login";
} catch (IncorrectCredentialsException e){//密码不存在
model.addAttribute("msg","密码错误");
return "login";
}
}
- 注销用户
public String logout(){
/*subject的实现类DelegatingSubject的logout方法,将本subject对象的session清空了
即使session托管给了redis ,redis有很多个浏览器的session
只要调用退出方法,此subject的、此浏览器的session就没了*/
SecurityUtils.getSubject().logout();
return "login";
}
获取当前登录用户
SecurityUtils.getSubject().getPrincipal();