Shiro
发表于|更新于
|字数总计:4.2k|阅读时长:15分钟|阅读量:
简述
程序的访问安全性一直是开发的重要环节,JavaEE开源世界中有两大常用的安全框架:Apache Shiro 和 Spring Security。前者结构简单容易上手,后者结构复杂功能强大,多年来各有大量的支持者。 Shiro可以非常容易的开发出足够好的应用,其不仅可以用在JavaSE环境,也可以用在JavaEE环境。Shiro可以帮助我们完成:认证、授权、加密、会话管理、与Web集成、缓存等。而且Shiro的API也是非常简单。
四大核心功能
验证(Authentication)、授权(Authorization)、会话管理(Session Management)和加密管理(Cryptography)。
Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;
Web Support:Web支持,可以非常容易的集成到Web环境;
Caching:缓存,比如用户登录后,其用户信息、拥有的角色/权限不必每次去查,这样可以提高效率;
Concurrency:shiro支持多线程应用的并发验证,即如在一个线程中开启另一个线程,能把权限自动传播过去; Testing:提供测试支持;
Run As:允许一个用户假装为另一个用户(如果他们允许)的身份进行访问;
Remember Me:记住我,这个是非常常见的功能,即一次登录后,下次再来的话不用登录了。
外部结构(应用程序角度)
可以看到:应用代码直接交互的对象是Subject,也就是说Shiro的对外API核心就是Subject;
(1)Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给
(2)SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
(3)Realm:安全域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操可以把Realm看成DataSource,即安全数据源。
内部架构
Subject:主体,可以看到主体可以是任何可以与应用交互的“用户”;
SecurityManager:相当于SpringMVC中的DispatcherServlet或者Struts2中的FilterDispatcher;是Shiro的心脏;所有具体的交互都通过SecurityManager进行控制;它管理着所有Subject、且负责进行认证和授权、及会话、缓存的管理。
Authenticator:认证器,负责主体认证的,这是一个扩展点,如果用户觉得Shiro默认的不好,可以自定义实现;其需要认证策略(Authentication Strategy),即什么情况下算用户认证通过了;
Authorizer:授权器,或者访问控制器,用来决定主体是否有权限进行相应的操作;即控制着用户能访问应用中的哪些功能;
Realm:可以有1个或多个Realm,可以认为是安全实体数据源,即用于获取安全实体的;可以是JDBC实现,也可以是LDAP实现,或者内存实现等等;由用户提供;注意:Shiro 不知道你的用户/权限存储在哪及以何种格式存储;所以我们一般在应用中都需要实现自己的Realm;
SessionManager:如果写过Servlet就应该知道Session的概念,Session需要有人去管理它的生命周期,这个组件就是SessionManager;而Shiro并不仅仅可以用在Web环境,也可以用在如普通的JavaSE环境、EJB等环境;Shiro抽象了一个自己的Session来管理主体与应用之间交互的数据;
SessionDAO:数据访问对象,用于会话的CRUD,比如我们想把Session保存到数据库,那么可以实现自己的SessionDAO,通过如JDBC写到数据库;比如想把Session放到Memcached中,可以实现自己的Memcached SessionDAO;另外SessionDAO中可以使用Cache进行缓存,以提高性能;
身份验证
身份验证,即在应用中谁能证明他就是他本人。一般提供如他们的身份ID一些标识信息来表明他就是他本人,如提供身份证,用户名/密码来证明。 在shiro中,用户需要提供principals (身份)和credentials(证明)给shiro,从而应用能验证用户身份:
principals:身份,即主体的标识属性,可以是任何东西,如用户名、邮箱等,唯一即可。一个主体可以有多个principals,但只有一个Primary principals,一般是用户名/密码/手机号。
credentials:证明/凭证,即只有主体知道的安全值,如密码/数字证书等。 最常见的principals和credentials组合就是用户名/密码了。
SecurityUtils:是一个抽象的工具类,提供了SecurityManager实例的保存和获取方法,以及创建Subject的方法。
UsernamePasswordToken:是一个简单的包含username及password即用户名及密码的登录验证用token,这个类同时继承了HostAuthenticationToken及RememberMeAuthenticationToken,主要包含用户名,密码,是否记住token以及验证来源的host主机地址。
核心过滤器
配置缩写 |
过滤器 |
|
anon |
AnonymousFilter |
指定url可以匿名访问 |
authc |
FormAuthenticationFilter |
指定url需要form表单登录,默认会从请求中获取username、password,rememberMe等参数并尝试登录,如果登录不了就会跳转到loginUrl配置的路径。我们也可以用这个过滤器做默认的登录逻辑,但是一般都是我们自己在控制器写登录 |
authcBasic |
BasicHttpAuthenticationFilter |
指定url需要basic登录 |
logout |
LogoutFilter |
登出过滤器,配置指定url就可以实现退出功能,非常方便 |
noSessionCreation |
noSessionCreation |
禁止创建会话 |
perms |
PermissionsAuthorizationFilter |
需要指定权限才能访问 |
rest |
HttpMethodPermissionFilter |
将http请求方法转化成相应的动词来构造一个权限字符串 |
roles |
RolesAuthorizationFilter |
需要指定的角色才能访问 |
ssl |
SslFilter |
需要https请求才能访问 |
user |
UserFilter |
需要已登录或“记住我”的用户才能访问 |
网页简易demo
两个用户,一个张三(zhangsan)一个管理员(admin),分别有不同的权限
导包
1 2 3 4 5 6
| <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring-boot-web-starter</artifactId> <version>1.4.1</version> </dependency>
|
继承身份验证安全域
自定义安全域继承身份验证安全域
有两个作用,
1.获取身份验证信息(账号密码) 并且 验证用户信息
2.权限分配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65
| @Component public class MyRealm extends AuthorizingRealm {
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { UsernamePasswordToken token= (UsernamePasswordToken)authenticationToken;
String username=token.getUsername();
String dbusername=username; if(!"admin".equals(dbusername)&&!"zhangsan".equals(dbusername)){ throw new UnknownAccountException("账号错误"); }
String dbpassword="123456";
Object objectPwd=new SimpleHash("MD5",dbpassword,"",1);
return new SimpleAuthenticationInfo(dbusername,objectPwd.toString(),this.getName()); }
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { Object username=principalCollection.getPrimaryPrincipal();
SimpleAuthorizationInfo simpleAuthorizationInfo=new SimpleAuthorizationInfo();
Set<String> roles=new HashSet<String>();
if("admin".equals(username)){ roles.add("admins"); roles.add("users"); }else if ("zhangsan".equals(username)){ roles.add("users"); }
Set<String>psermission=new HashSet<String>(); if("admin".equals(username)){ psermission.add("admin:add"); } simpleAuthorizationInfo.setRoles(roles); simpleAuthorizationInfo.setStringPermissions(psermission); return simpleAuthorizationInfo; }
}
|
自定义安全管理器ShiroConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
| @Configuration public class ShiroConfig {
@Bean public MyRealm realm() { return new MyRealm(); }
@Bean public DefaultWebSecurityManager securityManager(Realm realm){ DefaultWebSecurityManager defaultWebSecurityManager=new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(realm); return defaultWebSecurityManager; }
@Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean=new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);
shiroFilterFactoryBean.setLoginUrl("/");
shiroFilterFactoryBean.setSuccessUrl("/success");
shiroFilterFactoryBean.setUnauthorizedUrl("/noPermission");
Map<String,String> map=new HashMap<>(); map.put("/login","anon");
shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; }
@Bean public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; }
@Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor=new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
|
Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| @Controller public class UserController { @RequestMapping("/") public String index(){ return "login"; }
@RequestMapping("/login") public String login(String username, String password, Model model){ Subject subject= SecurityUtils.getSubject();
{ UsernamePasswordToken token=new UsernamePasswordToken(); token.setUsername(username); token.setPassword(password.toCharArray());
try { subject.login(token); } catch (UnknownAccountException ex){ model.addAttribute("errorMsg",ex.getMessage()); return "login"; } catch (LockedAccountException ex){ model.addAttribute("errorMsg",ex.getMessage()); return "login"; } catch (IncorrectCredentialsException ex){ model.addAttribute("errorMsg","密码不正确"); return "login"; } } return "redirect:/success"; }
@RequestMapping("/success") public String success(){ return "success"; }
@RequestMapping("/noPermission") public String noPermission(){ return "noPermission"; }
@RequestMapping("/user/test") @RequiresRoles("users") @ResponseBody public String userTest(){ return "这是普通用户请求"; }
@RequestMapping("/admin/test") @RequiresRoles("admins") @ResponseBody public String adminTest(){ return "这是管理员的adminTest请求"; }
@RequestMapping("/admin/add") @RequiresPermissions("admin:add") @ResponseBody public String adminAdd(){ return "这是管理员adminAdd请求"; }
@ExceptionHandler(value = {Exception.class}) public String myError(Throwable throwable){ System.out.println(throwable.getClass()); System.out.println("---------------------------------"); return "nopermission"; } }
|
定义页面
login.html
1 2 3 4 5 6
| <form action="login" method="post"> 账号<input type="text" name="username"><br> 密码<input type="text" name="password" id="password"><br> <input type="submit" value="登录" id="loginBut"> </form> <span style="color: red" th:text="${errorMessage}"></span>
|
nopermission.html
success.html
张三能够访问,/user/test
管理员能够访问/admin/add
,/admin/test
,/user/test
大致流程
shiro 运行大致流程
Shiro配置类(安全域、安全管理器、权限拦截过滤器、注解以及AOP支持)
身份认证
用户访问Controller
后触发subject.login
触发后进入自定义安全域的身份验证(安全域需要继承AuthorizingRealm
),一顿操作后成功则返回认证信息authenticationInfo
,否则不返回
授权
每个Controller
都有对应的授权页面
在安全区域授权
1 2 3 4 5 6 7 8
| //获取用户名 String username=principalCollection.getPrimaryPrincipal().toString(); //创建授权对象 SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo(); // 赋给授权 对象角色 authorizationInfo.addRole(角色); // 添加权限 authorizationInfo.addStringPermission(权限);
|
Controller
页面的每个对应的权限与角色
注解可以放在类上或者方法上
1 2 3 4 5 6
| @RequiresPermissions("org") @RequestMapping("/index") public String index() { return "org/index"; }
|
1 2 3 4 5
| @RequiresRoles("user") @RequestMapping("/user-list") public String userlist(Model model ){ return "security/user-list"; }
|
主要配置
shiro的主要配置
配置类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| @Configuration public class ShiroConfiguration { @Bean public MyRealm realm() { MyRealm myRealm = new MyRealm(); HashedCredentialsMatcher credentialMatcher = new HashedCredentialsMatcher(); credentialMatcher.setHashAlgorithmName("MD5"); credentialMatcher.setHashIterations(1024); myRealm.setCredentialsMatcher(credentialMatcher); return myRealm; }
@Bean public DefaultWebSecurityManager securityManager(Realm realm){ DefaultWebSecurityManager defaultWebSecurityManager=new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(realm); return defaultWebSecurityManager; }
@Bean public ShiroFilterChainDefinition shiroFilterChainDefinition() { DefaultShiroFilterChainDefinition chain = new DefaultShiroFilterChainDefinition(); chain.addPathDefinition("/login", "anon"); chain.addPathDefinition("/deny", "anon"); chain.addPathDefinition("/menus", "anon"); chain.addPathDefinition("/logout", "logout"); chain.addPathDefinition("/**/*", "authc"); return chain; }
@Bean public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){ DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator=new DefaultAdvisorAutoProxyCreator(); defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; }
@Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor=new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
|
访问控制过滤器还能在配置文件里面配置
1 2
| shiro.loginUrl=/login shiro.successUrl=/index
|
授权与身份验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| @Component public class MyRealm extends AuthorizingRealm { @Autowired private UserService userService; @Autowired private PermissionService permissionService;
@Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { String username=principalCollection.getPrimaryPrincipal().toString(); System.out.println("获取用户名" + username);
User user = userService.findByUsername(username);
SimpleAuthorizationInfo authorizationInfo=new SimpleAuthorizationInfo(); System.out.println("创建授权对象"+ authorizationInfo);
for (Role role : user.getRoles()) { authorizationInfo.addRole(role.getName()); } List<Permission> permissionList=permissionService.findPermissionsByUsername(username);
for (Permission permission : permissionList) { authorizationInfo.addStringPermission(permission.getCode()); for (Permission child : permission.getChildren()) { authorizationInfo.addStringPermission(child.getCode()); } }
return authorizationInfo; }
@Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
Object principal = authenticationToken.getPrincipal(); System.out.println("获取用户输入的身份标识" + principal);
if (principal != null){
String username=principal.toString(); User user = userService.findByUsername(username); if (user==null){ throw new UnknownAccountException("用户名不正确"); }else if (user.getStatus() == 0){ throw new LockedAccountException("账户已经锁定"); }else { SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( username, user.getPassword(), ByteSource.Util.bytes(user.getUsername()), this.getName()); return authenticationInfo; } } return null; } }
|