首先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的代碼
