首先pom中添加所需jar包:
<!-- shiro start --> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.3</version> </dependency> <!-- shiro end -->
一、在web.xml配制shiroFilter
<!-- 配置Shiro过滤器,先让Shiro过滤系统接收到的请求 --> <!-- 这里filter-name必须对应applicationContext.xml中定义的<bean id="shiroFilter"/> --> <!-- 使用[/*]匹配所有请求,保证所有的可控请求都经过Shiro的过滤 --> <!-- 通常会将此filter-mapping放置到最前面(即其他filter-mapping前面),以保证它是过滤器链中第一个起作用的 --> <filter> <filter-name>shiroFilter</filter-name> <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> <init-param> <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 --> <param-name>targetFilterLifecycle</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>shiroFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
二、java代码编写
1.User.java
package isa.blog.bin.model; import java.io.Serializable; import java.util.Date; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import org.hibernate.annotations.GenericGenerator; @Entity public class User implements Serializable { private static final long serialVersionUID = -5312120825533005238L; @Id @GeneratedValue(generator="system-uuid") @GenericGenerator(name="system-uuid",strategy="uuid") private String id; @Column(unique = true) private String userName; private String password; private Date createTime; private String email; @ManyToOne @JoinColumn private Role role; public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getUserName() { return userName; } public void setUserName(String userName) { this.userName = userName; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } }
2.Role.java
package isa.blog.bin.model; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import org.hibernate.annotations.GenericGenerator; @Entity public class Role implements Serializable { private static final long serialVersionUID = -3431097890965814550L; @Id @GeneratedValue(generator="system-uuid") @GenericGenerator(name="system-uuid",strategy="uuid") private String id; private String roleName; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } }
3.Permission.java
package isa.blog.bin.model; import java.io.Serializable; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import org.hibernate.annotations.GenericGenerator; @Entity public class Permission implements Serializable { private static final long serialVersionUID = 7808624074545962407L; @Id @GeneratedValue(generator="system-uuid") @GenericGenerator(name="system-uuid",strategy="uuid") private String id; private String permissionName; @ManyToOne @JoinColumn private Role role; public String getId() { return id; } public void setId(String id) { this.id = id; } public String getPermissionName() { return permissionName; } public void setPermissionName(String permissionName) { this.permissionName = permissionName; } public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } }
4.UserDao.java
package isa.blog.bin.dao; import org.springframework.data.jpa.repository.JpaRepository; import isa.blog.bin.model.User; public interface UserDao extends JpaRepository<User, String> { User findByUserNameAndPassword(String userName, String password); User findOneByUserName(String userName); }
5.PermissionDao.java
package isa.blog.bin.dao; import java.util.Set; import org.springframework.data.jpa.repository.JpaRepository; import isa.blog.bin.model.Permission; import isa.blog.bin.model.Role; public interface PermissionDao extends JpaRepository<Permission, String> { Set<Permission> findByRole(Role role); }
6.MyRealm.java
package isa.blog.bin.commons; import java.util.HashSet; import java.util.Set; 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.authz.AuthorizationInfo; import org.apache.shiro.authz.SimpleAuthorizationInfo; import org.apache.shiro.realm.AuthorizingRealm; import org.apache.shiro.subject.PrincipalCollection; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import isa.blog.bin.dao.PermissionDao; import isa.blog.bin.dao.UserDao; import isa.blog.bin.model.Permission; import isa.blog.bin.model.User; @Transactional public class MyRealm extends AuthorizingRealm{ @Autowired private UserDao userDao; @Autowired private PermissionDao permissionDao; /** * 为当前登录的用户授予角色和权限 */ @Override public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { String userName = (String)principals.getPrimaryPrincipal(); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); try{ authorizationInfo.addRole(userDao.findOneByUserName(userName).getRole().getRoleName()); Set<Permission> permissions = permissionDao.findByRole(userDao.findOneByUserName(userName).getRole()); Set<String> strs = new HashSet<String>(); for (Permission permission : permissions) { strs.add(permission.getPermissionName()); } authorizationInfo.addStringPermissions(strs); }catch(Exception e){ e.printStackTrace(); } return authorizationInfo; } /** * 验证当前登录的用户 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { String userName = (String)token.getPrincipal(); User user = userDao.findOneByUserName(userName); if(user!=null){ AuthenticationInfo authcInfo=new SimpleAuthenticationInfo(user.getUserName(), user.getPassword(),"xx"); return authcInfo; }else{ return null; } } }
三、配置文件applicationContext-shiro.xml
注意web.xml引入形式为:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath*:/applicationContext*.xml</param-value> </context-param>
applicationContext-shiro.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- 缓存管理器 --> <bean id="cacheManager" class="org.apache.shiro.cache.MemoryConstrainedCacheManager"/> <!-- 继承自AuthorizingRealm的自定义Realm,即指定Shiro验证用户登录的类为自定义的ShiroDbRealm.java --> <bean id="myRealm" class="isa.blog.bin.commons.MyRealm"/> <!-- 踢出用户 --> <bean id="sessionDAO" class="org.apache.shiro.session.mgt.eis.MemorySessionDAO" /> <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager"> <property name="sessionDAO" ref="sessionDAO" /> </bean> <!-- Shiro默认会使用Servlet容器的Session,可通过sessionMode属性来指定使用Shiro原生Session --> <!-- 即<property name="sessionMode" value="native"/>,详细说明见官方文档 --> <!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 --> <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager"> <property name="realm" ref="myRealm"/> <property name="sessionManager" ref="sessionManager" /> <!-- 使用下面配置的缓存管理器 --> <property name="cacheManager" ref="cacheManager"/> </bean> <!-- Shiro主过滤器本身功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行 --> <!-- Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持 --> <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <!-- Shiro的核心安全接口,这个属性是必须的 --> <property name="securityManager" ref="securityManager"/> <!-- 要求登录时的链接(可根据项目的URL进行替换),非必须的属性,默认会自动寻找Web工程根目录下的"/login.jsp"页面 --> <property name="loginUrl" value="/views/login.html"/> <!-- 登录成功后要跳转的连接(本例中此属性用不到,因为登录成功后的处理逻辑在LoginController里硬编码为main.jsp了) --> <!-- <property name="successUrl" value="/index.html"/> --> <!-- 用户访问未对其授权的资源时,所显示的连接 --> <!-- 若想更明显的测试此属性可以修改它的值,如unauthor.jsp,然后用[玄玉]登录后访问/admin/listUser.jsp就看见浏览器会显示unauthor.jsp --> <property name="unauthorizedUrl" value="/unauthorized.html"/> <!-- Shiro连接约束配置,即过滤链的定义 --> <!-- 此处可配合我的这篇文章来理解各个过滤连的作用http://blog.csdn.net/jadyer/article/details/12172839 --> <!-- 下面value值的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 --> <!-- anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 --> <!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter --> <property name="filterChainDefinitions"> <value> /frame/user/login/** = anon /frame/user/register/** = anon /frame/kaptcha** = anon /css/** = anon /img/** = anon /js/** = anon /json/** = anon /Scripts/** = anon /views/login.html = anon /views/register.html = anon # /frame/essay = roles[member] # /views/addBlog.html = roles[admin] # /frame/deleteEssay/** = perms[删除博客] # /frame/deleteEssay/** = roles[admin] # 必须放在最后 /** = authc </value> </property> </bean> <!-- 保证实现了Shiro内部lifecycle函数的bean执行 --> <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/> <!-- 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 --> <!-- 配置以下两个bean即可实现此功能 --> <!-- Enable Shiro Annotations for Spring-configured beans. Only run after the lifecycleBeanProcessor has run --> <!-- 由于本例中并未使用Shiro注解,故注释掉这两个bean(个人觉得将权限通过注解的方式硬编码在程序中,查看起来不是很方便,没必要使用) --> <!-- <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator" depends-on="lifecycleBeanPostProcessor"/> <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor"> <property name="securityManager" ref="securityManager"/> </bean> --> </beans>
以下代码根据实际项目更改
UserController.java
package isa.blog.bin.controller; import java.awt.image.BufferedImage; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.util.HashMap; import java.util.Map; import javax.imageio.ImageIO; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import com.google.code.kaptcha.Constants; import com.google.code.kaptcha.Producer; import isa.blog.bin.commons.JsonUtil; import isa.blog.bin.commons.Result; import isa.blog.bin.dto.UserDto; import isa.blog.bin.service.UserService; @RestController @RequestMapping(value="/frame") public class UserController { @Autowired private UserService userService; @Autowired private Producer producer; private Map<String, String> map; @RequestMapping(value="/user/register/{kaptchaCode}", method=RequestMethod.POST, produces=JsonUtil.JSON) public Result register(@RequestBody UserDto userDto, @PathVariable String kaptchaCode) { return userService.register(userDto, map, kaptchaCode); } @RequestMapping(value="/user/oneUser/{userId}", method=RequestMethod.GET, produces=JsonUtil.JSON) public Result getOneUser(@PathVariable String userId) { return userService.getOneUser(userId); } @RequestMapping(value="/user/login/{userName}/{password}/{kaptchaCode}", method=RequestMethod.GET, produces=JsonUtil.JSON) public Result login(@PathVariable String userName, @PathVariable String password, @PathVariable String kaptchaCode) throws UnsupportedEncodingException { return userService.login(URLDecoder.decode(userName, "UTF-8"), password, map, kaptchaCode); } @RequestMapping(value="/user/logout", method=RequestMethod.GET, produces=JsonUtil.JSON) public void logout() { userService.logout(); } @RequestMapping(value="/user/userDetails_userName", method=RequestMethod.GET, produces=JsonUtil.JSON) public Result getOneUserName() { return userService.getOneUserName(); } @RequestMapping(value="/user/userDetails", method=RequestMethod.GET, produces=JsonUtil.JSON) public Result getOneUserDetails() { String userId = userService.currentUserId(); return userService.getOneUser(userId); } @RequestMapping(value="/user/updateUserDetails", method=RequestMethod.POST, produces=JsonUtil.JSON) public Result updateUserDetails(@RequestBody UserDto userDto) { String userId = userService.currentUserId(); return userService.updateUserDetails(userId, userDto); } @RequestMapping("/kaptcha") public void initCaptcha(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpSession session = request.getSession(); response.setDateHeader("Expires", 0); response.setHeader("Cache-Control", "no-store, no-cache, must-revalidate"); response.addHeader("Cache-Control", "post-check=0, pre-check=0"); response.setHeader("Pragma", "no-cache"); response.setContentType("image/jpeg"); String capText = producer.createText(); session.setAttribute(Constants.KAPTCHA_SESSION_KEY, capText); BufferedImage bi = producer.createImage(capText); ServletOutputStream out = response.getOutputStream(); ImageIO.write(bi, "jpg", out); try { out.flush(); } finally { String kaptchaCode = (String)session.getAttribute(Constants.KAPTCHA_SESSION_KEY); map = new HashMap<String, String>(); map.put("kaptchaCode", kaptchaCode); out.close(); } } }
UserService.java
package isa.blog.bin.service; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Map; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.session.Session; import org.apache.shiro.session.mgt.eis.SessionDAO; import org.apache.shiro.subject.PrincipalCollection; import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.support.DefaultSubjectContext; import org.modelmapper.ModelMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import isa.blog.bin.commons.MD5Util; import isa.blog.bin.commons.Result; import isa.blog.bin.dao.UserDao; import isa.blog.bin.dto.UserDto; import isa.blog.bin.model.User; @Service @Transactional public class UserService { ModelMapper modelMapper = new ModelMapper(); @Autowired private UserDao userDao; @Autowired private SessionDAO sessionDAO; /** * 注册 * @param userDto * @param map * @param kaptchaCode * @return */ public Result register(UserDto userDto, Map<String, String> map, String kaptchaCode) { Result result = new Result(); if (!map.get("kaptchaCode").equals(kaptchaCode)) { result.setSuccess(false); result.setMessage("验证码错误"); return result; } User user = userDao.findOneByUserName(userDto.getUserName()); if (user != null) { result.setSuccess(false); result.setMessage("用户名已存在"); return result; } user = modelMapper.map(userDto, User.class); user.setPassword(MD5Util.MD5(userDto.getPassword())); user.setCreateTime(new Date()); userDao.save(user); result.setId(user.getId()); result.setSuccess(true); result.setMessage("注册成功"); return result; } /** * 获取一个用户详情 * @param userId * @return */ public Result getOneUser(String userId) { Result result = new Result(); User user = userDao.findOne(userId); UserDto userDto = modelMapper.map(user, UserDto.class); List<UserDto> datas = new ArrayList<UserDto>(); datas.add(userDto); result.setSuccess(true); result.setDatas(datas); return result; } /** * 登录 * @param userName * @param password * @param map * @param kaptchaCode * @return */ public Result login(String userName, String password, Map<String, String> map, String kaptchaCode) { Result result = new Result(); if (!map.get("kaptchaCode").equals(kaptchaCode)) { result.setSuccess(false); result.setMessage("验证码错误"); return result; } Subject subject = SecurityUtils.getSubject(); UsernamePasswordToken token = new UsernamePasswordToken(userName, MD5Util.MD5(password)); //踢除用户 this.kickOutUser(token); try{ subject.login(token); Session session = subject.getSession(); System.out.println("sessionId:"+session.getId()); System.out.println("sessionHost:"+session.getHost()); System.out.println("sessionTimeout:"+session.getTimeout()); result.setMessage("登录成功"); System.out.println(userName+"登录成功"); List<UserDto> userDtos = new ArrayList<UserDto>(); User user = this.userDao.findOneByUserName(userName); userDtos.add(modelMapper.map(user, UserDto.class)); result.setDatas(userDtos); result.setSuccess(true); return result; }catch(Exception e){ e.printStackTrace(); result.setSuccess(false); result.setMessage("用户名或密码错误!"); System.out.println("用户名或密码错误!"); return result; } } /** * 获得当前用户id * @return */ public String currentUserId() { Subject subject = SecurityUtils.getSubject(); PrincipalCollection collection = subject.getPrincipals(); if (null != collection && !collection.isEmpty()) { String userName = (String) collection.iterator().next(); return userDao.findOneByUserName(userName).getId(); } return null; } /** * 踢除用户 * http://www.ithao123.cn/content-7174367.html */ public void kickOutUser(UsernamePasswordToken token){ String loginName = token.getUsername(); Collection<Session> sessions = sessionDAO.getActiveSessions(); for(Session session:sessions){ if(loginName.equals(String.valueOf(session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY)))) { //设置session立即失效,即将其踢出系统 session.setTimeout(0); } } } /** * 退出登录 */ public void logout() { Subject subject = SecurityUtils.getSubject(); if (subject.isAuthenticated()) { // session 会销毁,在SessionListener监听session销毁,清理权限缓存 subject.logout(); } } /** * 获取当前用户名 * @return */ public Result getOneUserName() { Subject subject = SecurityUtils.getSubject(); PrincipalCollection collection = subject.getPrincipals(); Result result = new Result(); if (null != collection && !collection.isEmpty()) { String userName = (String) collection.iterator().next(); result.setSuccess(true); List<String> datas = new ArrayList<String>(); datas.add(userName); result.setDatas(datas); return result; } result.setSuccess(false); return result; } /** * 修改用户信息 * @param userId * @param userDto * @return */ public Result updateUserDetails(String userId, UserDto userDto) { Result result = new Result(); User user = modelMapper.map(userDto, User.class); user.setId(userId); user.setPassword(userDao.findOne(userId).getPassword()); user.setCreateTime(userDao.findOne(userId).getCreateTime()); user.setRole(userDao.findOne(userId).getRole()); if (!userDto.getUserName().equals(userDao.findOne(userId).getUserName())) { result.setMessage("修改成功,立即重新登录!"); } else { result.setMessage("修改成功!"); } userDao.save(user); result.setId(userId); result.setSuccess(true); return result; } }
需要注意,前端需要编写全局ajax以便对用户友好提示权限相关信息,
统一处理页面登录超时和无权限情况(一般在每一个页面都会调用的js文件中编写以便覆盖完整)
//统一处理页面登录超时和无权限情况 jQuery(function($){ // 备份jquery的ajax方法 var _ajax = $.ajax; // 重写ajax方法 $.ajax=function(opt){ var _error = opt && opt.error || function(a, b){}; var _opt = $.extend(opt, { error:function(data, textStatus){ // 如果后台将请求重定向到了登录页,则data里面存放的就是登录页的源码,这里需要找到data是登录页的证据(标记) if(data.responseText.indexOf("无此权限") > 0) { alert("无此权限"); return; } else if (data.responseText.indexOf("请输入用户名") > 0) { alert("登录超时,请重新登录!"); window.location.href = '/views/login.html'; return; } else { alert("error"); return; } _error(data, textStatus); } }); _ajax(_opt); }; });
data.responseText获取得到的是applicationContext-shiro.xml里的配置
登录超时:
<property name="loginUrl" value="/views/login.html"/>
无权限:
<property name="unauthorizedUrl" value="/unauthorized.html"/>
对应页面的html的代码