第一部分 什么是Apache Shiro
1、什么是 apache shiro :
Apache Shiro是一个功能强大且易于使用的Java安全框架,提供了认证,授权,加密,和会话管理
如同 spring security 一样都是是一个权限安全框架,但是与Spring Security相比,在于他使用了和比较简洁易懂的认证和授权方式。
2、Apache Shiro 的三大核心组件:
1、Subject :当前用户的操作
2、SecurityManager:用于管理所有的Subject
3、Realms:用于进行权限信息的验证
Subject:即当前用户,在权限管理的应用程序里往往需要知道谁能够操作什么,谁拥有操作该程序的权利,shiro中则需要通过Subject来提供基础的当前用户信息,Subject 不仅仅代表某个用户,也可以是第三方进程、后台帐户(Daemon Account)或其他类似事物。
SecurityManager:即所有Subject的管理者,这是Shiro框架的核心组件,可以把他看做是一个Shiro框架的全局管理组件,用于调度各种Shiro框架的服务。
Realms:Realms则是用户的信息认证器和用户的权限人证器,我们需要自己来实现Realms来自定义的管理我们自己系统内部的权限规则。
3、Authentication 和 Authorization
在shiro的用户权限认证过程中其通过两个方法来实现:
1、Authentication:是验证用户身份的过程。
2、Authorization:是授权访问控制,用于对用户进行的操作进行人证授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。
4、其他组件:
除了以上几个组件外,Shiro还有几个其他组件:
1、SessionManager :Shiro为任何应用提供了一个会话编程范式。
2、CacheManager :对Shiro的其他组件提供缓存支持。
第二部分 Apache Shiro 整合Spring的Web程序构建
1、准备工具:
持久层框架:Hibernate4 这边我使用了hibernate来对数据持久层进行操作
控制显示层框架:SpringMVC 这边我使用了SpringMVC实际开发中也可以是其他框架
准备好所需要的jar放到项目中。
2、创建数据库:
首先需要四张表,分别为 user(用户)、role(角色)、permission(权限)、userRole(用户角色关系表)
这边分别创建四张表的实体类,通过Hiberante的hibernate.hbm2ddl.auto属性的update 来自动生成数据表结构。
/*** * 用户表 * * @author Swinglife * */ @Table(name = "t_user") @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.AUTO) Integer id; /** 用户名 **/ String username; /** 密码 **/ String password; /** 是否删除 **/ Integer isDelete; /** 创建时间 **/ Date createDate; //多对多用户权限表 @OneToMany(mappedBy = "user",cascade=CascadeType.ALL) List<UserRole> userRoles; 省略get set…. }
/**** * 角色表 * * @author Swinglife * */ @Entity @Table(name = "t_role") public class Role { @Id @GeneratedValue(strategy = GenerationType.AUTO) Integer id; /**角色名**/ String name; /**角色说明**/ String description; }
/**** * 权限表 * * @author Swinglife * */ @Entity @Table(name = "t_permission") public class Permission { @Id @GeneratedValue(strategy = GenerationType.AUTO) Integer id; /**token**/ String token; /**资源url**/ String url; /**权限说明**/ String description; /**所属角色编号**/ Integer roleId; }
/*** * 用户角色表 * * @author Swinglife * */ @Entity @Table(name = "t_user_role") public class UserRole { @Id @GeneratedValue(strategy = GenerationType.AUTO) Integer id; @ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name = "userId", unique = true) User user; @ManyToOne @JoinColumn(name = "roleId", unique = true) Role role; }
3、编写操作用户业务的Service:
@Service public class AccountService { /**** * 通过用户名获取用户对象 * * @param username * @return */ public User getUserByUserName(String username) { User user = (User) dao.findObjectByHQL("FROM User WHERE username = ?", new Object[] { username }); return user; } /*** * 通过用户名获取权限资源 * * @param username * @return */ public List<String> getPermissionsByUserName(String username) { System.out.println("调用"); User user = getUserByUserName(username); if (user == null) { return null; } List<String> list = new ArrayList<String>(); // System.out.println(user.getUserRoles().get(0).get); for (UserRole userRole : user.getUserRoles()) { Role role = userRole.getRole(); List<Permission> permissions = dao.findAllByHQL("FROM Permission WHERE roleId = ?", new Object[] { role.getId() }); for (Permission p : permissions) { list.add(p.getUrl()); } } return list; } // 公共的数据库访问接口 // 这里省略BaseDao dao的编写 @Autowired private BaseDao dao; }
4、编写shiro组件自定义Realm:
package org.swinglife.shiro; import java.util.List; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.AuthenticationInfo; import org.apache.shiro.authc.AuthenticationToken; import org.apache.shiro.authc.SimpleAuthenticationInfo; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.swinglife.model.User; import org.swinglife.service.AccountService; /**** * 自定义Realm * * @author Swinglife * */ public class MyShiroRealm extends AuthorizingRealm { /*** * 获取授权信息 */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection pc) { //根据自己系统规则的需要编写获取授权信息,这里为了快速入门只获取了用户对应角色的资源url信息 String username = (String) pc.fromRealm(getName()).iterator().next(); if (username != null) { List<String> pers = accountService.getPermissionsByUserName(username); if (pers != null && !pers.isEmpty()) { SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); for (String each : pers) { //将权限资源添加到用户信息中 info.addStringPermission(each); } return info; } } return null; } /*** * 获取认证信息 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken at) throws AuthenticationException { UsernamePasswordToken token = (UsernamePasswordToken) at; // 通过表单接收的用户名 String username = token.getUsername(); if (username != null && !"".equals(username)) { User user = accountService.getUserByUserName(username); if (user != null) { return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), getName()); } } return null; } /**用户的业务类**/ private AccountService accountService; public AccountService getAccountService() { return accountService; } public void setAccountService(AccountService accountService) { this.accountService = accountService; } }
上述类继承了Shiro的AuthorizingRealm类 实现了AuthorizationInfo和AuthenticationInfo两个方法,用于获取用户权限和认证用户登录信息
5、编写LoginController:
package org.swinglife.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.portlet.ModelAndView; import org.swinglife.model.User; import org.swinglife.service.AccountService; /**** * 用户登录Controller * * @author Swinglife * */ @Controller public class LoginController { /*** * 跳转到登录页面 * * @return */ @RequestMapping(value = "toLogin") public String toLogin() { // 跳转到/page/login.jsp页面 return "login"; } /*** * 实现用户登录 * * @param username * @param password * @return */ @RequestMapping(value = "login") public ModelAndView Login(String username, String password) { ModelAndView mav = new ModelAndView(); User user = accountService.getUserByUserName(username); if (user == null) { mav.setView("toLogin"); mav.addObject("msg", "用户不存在"); return mav; } if (!user.getPassword().equals(password)) { mav.setView("toLogin"); mav.addObject("msg", "账号密码错误"); return mav; } SecurityUtils.getSecurityManager().logout(SecurityUtils.getSubject()); // 登录后存放进shiro token UsernamePasswordToken token = new UsernamePasswordToken(user.getUsername(), user.getPassword()); Subject subject = SecurityUtils.getSubject(); subject.login(token); // 登录成功后会跳转到successUrl配置的链接,不用管下面返回的链接。 mav.setView("redirect:/home"); return mav; } // 处理用户业务类 @Autowired private AccountService accountService; }
6、编写信息认证成功后的跳转页面:
package org.swinglife.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class IndexController { @RequestMapping("home") public String index() { System.out.println("登录成功"); return "home"; } }
7、Shiro的配置文件.xml
<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/toLogin" /> <property name="successUrl" value="/home" /> <property name="unauthorizedUrl" value="/403" /> <property name="filterChainDefinitions"> <value> /toLogin = authc <!-- authc 表示需要认证才能访问的页面 --> /home = authc, perms[/home] <!-- perms 表示需要该权限才能访问的页面 --> </value> </property> </bean> <bean id="myShiroRealm" class="org.swinglife.shiro.MyShiroRealm"> <property name="accountService" ref="accountService" /> </bean> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myShiroRealm"></property> </bean> <bean id="accountService" class="org.swinglife.service.AccountService"></bean> <!-- <bean id="shiroCacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager"> <property name="cacheManager" ref="cacheManager" /> </bean> -->
loginUrl 用于配置登陆页
successUrl 用于配置登录成功后返回的页面,不过该参数只会在当登录页面中并没有任何返回页面时才会生效,否则会跳转到登录Controller中的指定页面。
unauthorizedUrl 用于配置没有权限访问页面时跳转的页面
filterChainDefinitions:apache shiro通过filterChainDefinitions参数来分配链接的过滤,资源过滤有常用的以下几个参数:
1、authc 表示需要认证的链接
2、perms[/url] 表示该链接需要拥有对应的资源/权限才能访问
3、roles[admin] 表示需要对应的角色才能访问
4、perms[admin:url] 表示需要对应角色的资源才能访问
8、登陆页login.jsp
<body> <h1>user login</h1> <form action="login" method="post"> username:<input type="text" name="username"><p> password:<input type="password" name="password"> <p> ${msg } <input type="submit" value="submit"> </form> </body>
9、运行程序
在数据库中添加一条用户、角色、以及权限数据,并且在关联表中添加一条关联数据:
在浏览器中访问: home页面 就会跳转到登录页面:
最后输入 账号密码 就会跳转到登录成功页面。
关于如何实现同一时刻只有一个相同账户登录的问题:
DefaultWebSecurityManager securityManager = (DefaultWebSecurityManager) SecurityUtils.getSecurityManager(); DefaultWebSessionManager sessionManager = (DefaultWebSessionManager)securityManager.getSessionManager(); Collection<Session> sessions = sessionManager.getSessionDAO().getActiveSessions();
通过以上代码得到所有的session,循环遍历session:
for(Session session:sessions){ System.out.println("登录ip:"+session.getHost()); System.out.println("登录用户"+session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY)); System.out.println("最后操作日期:"+session.getLastAccessTime()); }
然后自行判断是否存在某一个账户,如果不存在可以登录,如果存在return null
如果只进入了doGetAuthenticationInfo方法,没有进入doGetAuthorizationInfo方法,并且配置无异常,无遗漏,可能是因为在过滤器中权限配置没有配置到你即将进入的页面,如果没有配置perms[],则shiro不会自行进入doGetAuthorizationInfo方法进行权限判断。
shiro jar:http://download.csdn.net/detail/swingpyzf/8766673
项目源码:github:https://github.com/swinglife/shiro_ex
本文转自:http://blog.csdn.net/swingpyzf/article/details/46342023/
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/11504.html