軟件152 尹以操
springboot不像springmvc,它沒有xml配置文件,那該如何配置shiro呢,其實也不難,用java代碼+注解來解決這個問題。僅以此篇記錄我對shiro的學習,如有對過客造成不便,實在抱歉!
一、加入jar包
既然要用到shiro,當然要加入它的jar包咯,在pom.xml中jar包依賴中加入:
<dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.2.2</version> </dependency>
二、寫實體類
這需要三個實體類,hibernate自動生成5個表
User實體(用戶):
package com.cy.coo.bean; import java.util.List; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import com.fasterxml.jackson.annotation.JsonBackReference; @Entity public class User { @Id @GeneratedValue private Integer user_id;//用戶序號 @Column(unique=true) private String name;//賬戶 private String password;//密碼 private String salt;//鹽 private Integer state;//用戶狀態 private String createtime;//創建時間 @ManyToMany(fetch=FetchType.EAGER)//立即從數據庫中進行加載數據; @JoinTable(name="User_Role",joinColumns={@JoinColumn(name="user_id")}, inverseJoinColumns={@JoinColumn(name="role_id")}) private List<Role> roleList; @JsonBackReference public List<Role> getRoleList(){ return roleList; } public void setRoleList(List<Role> roleList){ this.roleList=roleList; } 注:其它getter和setter省略 }
關於為什么要在getRolelist這個方法上加上@JsonBackReference注解,可以查看這篇文章http://blog.csdn.net/maxu12345/article/details/45538157
Role實體(角色):
package com.cy.coo.bean; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import com.fasterxml.jackson.annotation.JsonBackReference; @Entity public class Role implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue private Integer role_id;//角色序號 private String role_name;//角色名稱 private String role_description;//角色描述 @ManyToMany @JoinTable(name = "User_Role", joinColumns = { @JoinColumn(name = "role_id") }, inverseJoinColumns = { @JoinColumn(name = "user_id") }) private List<User> userList=new ArrayList<>(); @ManyToMany(fetch=FetchType.EAGER) @JoinTable(name="Role_Function",joinColumns={@JoinColumn(name="role_id")},inverseJoinColumns={ @JoinColumn(name="function_id")}) private List<Function> functionList=new ArrayList<>(); @JsonBackReference public List<Function> getFunctionList(){ return functionList; } public void setFunctionList(List<Function> functionList){ this.functionList=functionList; } @JsonBackReference public List<User> getUserList() { return userList; } public void setUserList(List<User> userList) { this.userList = userList; } public Integer getRole_id() { return role_id; } public void setRole_id(Integer role_id) { this.role_id = role_id; } public String getRole_name() { return role_name; } public void setRole_name(String role_name) { this.role_name = role_name; } public String getRole_description() { return role_description; } public void setRole_description(String role_description) { this.role_description = role_description; } }
Function實體(權限):
package com.cy.coo.bean; import java.util.List; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; @Entity public class Function { @Id @GeneratedValue private Integer function_id;//功能序號 private String permission;//權限字符串 @ManyToMany @JoinTable(name = "Role_Function", joinColumns = { @JoinColumn(name = "function_id") }, inverseJoinColumns = { @JoinColumn(name = "role_id") }) private List<Role> roleList; public List<Role> getRoleList() { return roleList; } public void setRoleList(List<Role> roleList) { this.roleList = roleList; } public Integer getFunction_id() { return function_id; } public void setFunction_id(Integer function_id) { this.function_id = function_id; } public String getPermission() { return permission; } public void setPermission(String permission) { this.permission = permission; } }
這幾個實體類的具體關系如下圖,也完美的解釋了為什么會生成5張表:
三、寫一個與前端交互的controller方法,service層的具體邏輯的方法
@PostMapping(value = "/logon") public Object logon(@RequestBody Login user) { return userService.login(user); }
這個方法就是將前端傳來的username和password封裝到Login類中,Login類也只有這兩個屬性,然后調用Service層的login方法來處理。下面是service的login方法:
/** * 用戶登錄 create by yyc 2017年5月12日下午4:31:26 */ @Override public Object login(Login user) { String username = user.getUsername().trim(); String password = user.getPassword().trim(); // 檢查空值 if (!CheckObjectField.CheckField(user)) { throw new ResultException(CheckObjectField.FieldName + "為空!"); } // 檢查用戶狀態 Integer userState = userRepository.findUserState(username); if (new Integer("1").equals(userState)) { throw new ResultException("該用戶已鎖定"); } // 1、獲取Subject實例對象 Subject currentUser = SecurityUtils.getSubject(); // 2、判斷當前用戶是否登錄 if (currentUser.isAuthenticated() == false) { // 3、將用戶名和密碼封裝到UsernamePasswordToken UsernamePasswordToken token = new UsernamePasswordToken(username, password); // 4、認證 try { currentUser.login(token);// 傳到MyAuthorizingRealm類中的方法進行認證 Session session = currentUser.getSession(); session.setAttribute("username", username); } catch (AuthenticationException e) { throw new ResultException("密碼或用戶名錯誤"); } } // 根據用戶名查詢角色信息 List<String> RoleNames = roleService.findRoleName(username); return new LoginReturn(username, RoleNames); }
service中主要是將用戶名和密碼封裝到shiro的UsernamePasswordToken中,然后將token對象放到SecurityUtils.getSubject()的login方法中,以便shiro認證登錄使用。認證失敗就會拋出AuthenticationException這個異常,就對異常進行相應的操作,這里的處理是拋出一個自定義異常ResultException。
四、寫我認為的shiro的核心類
package com.cy.coo.shiro; 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.apache.shiro.util.ByteSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.cy.coo.bean.Function; import com.cy.coo.bean.Role; import com.cy.coo.bean.User; import com.cy.coo.service.UserService; /** * * * @author E-mail:34782655@qq.com * @version 創建時間:2017年5月8日 上午10:50:50 * 類說明: * -- */ public class MyAuthorizingRealm extends AuthorizingRealm { private final static Logger logger=LoggerFactory.getLogger(MyAuthorizingRealm.class); @Autowired private UserService userService; //shiro的權限配置方法 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { logger.info("權限配置-->doGetAuthorizationInfo"); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); logger.info("----------------------------->"+principals.getPrimaryPrincipal()); User user=(User) principals.getPrimaryPrincipal(); for(Role role:user.getRoleList()){ authorizationInfo.addRole(role.getRole_name()); for(Function function:role.getFunctionList()){ authorizationInfo.addStringPermission(function.getPermission()); } } logger.info("用戶"+user.getName()+"具有的角色:"+authorizationInfo.getRoles()); logger.info("用戶"+user.getName()+"具有的權限:"+authorizationInfo.getStringPermissions()); return authorizationInfo; } //shiro的身份驗證方法 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { logger.info("正在驗證身份..."); SimpleAuthenticationInfo info=null; //將token轉換成UsernamePasswordToken UsernamePasswordToken upToken = (UsernamePasswordToken) token; //從轉換后的token中獲取用戶名 String username= upToken.getUsername(); logger.info("----->"+username); //查詢數據庫,得到用戶 User user=userService.findByName(username); if(user==null){ return null; } //得到加密密碼的鹽值 ByteSource salt = ByteSource.Util.bytes(user.getSalt()); // logger.info("加密密碼的鹽:"+salt); // //得到鹽值加密后的密碼:只用於方便數據庫測試,后期不會用到。 // Object md = new SimpleHash("MD5",upToken.getPassword(),salt,1024); // logger.info("鹽值加密后的密碼:"+md); info = new SimpleAuthenticationInfo( user, //用戶名 user.getPassword(), //密碼 salt, //加密的鹽值 getName() //realm name ); return info; } }
這個類繼承shiro的AuthorizingRealm ,主要有兩個方法,一個是權限配置,一個是身份認證,權限配置:當我們要用到權限時shiro會回調doGetAuthorizationInfo這個方法,對當前的用戶分配權限,這個方法中的嵌套for循環是怎么回事呢,其實就是將數據庫中的對應角色、權限放進shiro中,讓他來管理,這需要實體類User中有getRoleList()、getRole_name()和getFunctionList()、getPermission這幾個方法,這幾個個方法就是設計數據庫和實體類時的東西了,關於shiro權限相關的實體類在前面已經給出了。身份認證:在用戶登錄認證的時候回調,認證失敗就拋出AuthenticationException。
五、shiro配置類
package com.cy.coo.shiro; import java.util.LinkedHashMap; import java.util.Map; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; import org.apache.shiro.cache.ehcache.EhCacheManager; import org.apache.shiro.mgt.SecurityManager; import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor; import org.apache.shiro.spring.web.ShiroFilterFactoryBean; import org.apache.shiro.web.mgt.CookieRememberMeManager; import org.apache.shiro.web.mgt.DefaultWebSecurityManager; import org.apache.shiro.web.servlet.SimpleCookie; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.cache.ehcache.EhCacheManagerFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; @Configuration // 等價於beans public class ShiroConfig { private static final Logger log = LoggerFactory.getLogger(ShiroFilterFactoryBean.class); @Bean(name = "securityManager") public SecurityManager securityManager(@Qualifier("authRealm") MyAuthorizingRealm authRealm, @Qualifier("cookieRememberMeManager") CookieRememberMeManager cookieRememberMeManager) { log.info("securityManager()"); DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); // 設置realm. securityManager.setRealm(authRealm); // 設置rememberMe管理器 securityManager.setRememberMeManager(cookieRememberMeManager); return securityManager; } /** * realm * * @return */ @Bean(name = "authRealm") public MyAuthorizingRealm myAuthRealm( @Qualifier("hashedCredentialsMatcher") HashedCredentialsMatcher matcher, @Qualifier("ehCacheManager") EhCacheManager ehCacheManager) { log.info("myShiroRealm()"); MyAuthorizingRealm myAuthorizingRealm = new MyAuthorizingRealm(); // 設置密碼憑證匹配器 myAuthorizingRealm.setCredentialsMatcher(matcher); // myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); // 設置緩存管理器 myAuthorizingRealm.setCacheManager(ehCacheManager); return myAuthorizingRealm; } /** * 緩存管理器 * @return */ @Bean(value="ehCacheManager") public EhCacheManager ehCacheManager(@Qualifier("ehCacheManagerFactoryBean") EhCacheManagerFactoryBean bean) { log.info("ehCacheManager()"); EhCacheManager cacheManager = new EhCacheManager(); cacheManager.setCacheManagerConfigFile("classpath:ehcache-shiro.xml"); return cacheManager; } /** * cookie對象; * * @return */ @Bean public SimpleCookie rememberMeCookie() { log.info("rememberMeCookie()"); // 這個參數是cookie的名稱,對應前端的checkbox 的name = rememberMe SimpleCookie simpleCookie = new SimpleCookie("rememberMe"); // <!-- 記住我cookie生效時間30天(259200) ,單位秒;--> simpleCookie.setMaxAge(259200); return simpleCookie; } /** * 記住我管理器 cookie管理對象; * * @return */ @Bean(name = "cookieRememberMeManager") public CookieRememberMeManager rememberMeManager() { System.out.println("rememberMeManager()"); CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager(); cookieRememberMeManager.setCookie(rememberMeCookie()); return cookieRememberMeManager; } /** * 密碼匹配憑證管理器 * * @return */ @Bean(name = "hashedCredentialsMatcher") public HashedCredentialsMatcher hashedCredentialsMatcher() { log.info("hashedCredentialsMatcher()"); HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("MD5");// 散列算法:這里使用MD5算法; hashedCredentialsMatcher.setHashIterations(1024);// 散列的次數,比如散列兩次,相當於 // md5(md5("")); return hashedCredentialsMatcher; } /** * 開啟shiro aop注解支持. 使用代理方式;所以需要開啟代碼支持; Controller才能使用@RequiresPermissions * * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor( @Qualifier("securityManager") SecurityManager securityManager) { log.info("authorizationAttributeSourceAdvisor()"); AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean public ShiroFilterFactoryBean shiroFilter(@Qualifier("securityManager") SecurityManager securityManager) { log.info("shirFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); // 必須設置 SecurityManager shiroFilterFactoryBean.setSecurityManager(securityManager); // 攔截器. Map<String, String> map = new LinkedHashMap<String, String>(); map.put("/logout", "logout"); map.put("/login", "anon"); map.put("/logon", "anon"); map.put("/**", "authc"); // 如果不設置默認會自動尋找Web工程根目錄下的"/login.jsp"頁面 shiroFilterFactoryBean.setLoginUrl("/login"); // 登錄成功后要跳轉的鏈接 shiroFilterFactoryBean.setSuccessUrl("/index"); // 未授權界面; shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } }
這個沒什么好說的,最后一個類是shiro的過濾器配置。可以看到我在每個方法上面加了一個@Bean(name="..."),其實這是spring的注解,將這個類放到spring容器中管理,在方法形參中使用@Qualifier(...)來使用它,以致於我們在方法體中調用某個方法時就方面多了。
在這里,關於shiro在springboot中的基礎配置就完成了。下面是期間遇到的錯誤解決方案: