所需要的pom依赖:
<!-- spring 集成 shiro -->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>1.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
首先建立一个简单的springboot项目,这里数据持久化方式使用JPA,项目文件结构如下所示:
下面列出各个类的详细代码:
UserInfo:
package com.slf.firstappdemo.framework.shiro.domain; import javax.persistence.*; import java.io.Serializable; import java.util.List; @Entity public class UserInfo implements Serializable{ private static final long serialVersionUID = 1L; @Id@GeneratedValue private long id; //用户ID @Column(unique = true) private String username;//账号 private String name ;//名称 private String password;//密码 private String salt;//加密密码的盐,在这里salt主要是用来进行密码加密的,当然也可以使用明文进行编码测试,实际开发中还是建议密码进行加密。 private byte state;//用户状态:1:创建未认证(比如没有激活,没有输入验证码等) 等待验证的用户,1:正常状态,2:用户被锁定。 @ManyToMany(fetch = FetchType.EAGER)//立即从数据库进行加载数据 @JoinTable(name="SysUserrole",joinColumns = { @JoinColumn( name="uid")}, inverseJoinColumns = {@JoinColumn(name="roleId")} ) private List<SysRole> roleList; //getter and setter /** * 密码盐. * @return */ public String getCredentialsSalt(){ return this.username+this.salt; } //toString }
SysRole:
package com.slf.firstappdemo.framework.shiro.domain; import javax.persistence.*; import java.io.Serializable; import java.util.List; /** * 系统角色实体类; * @author Angel(QQ:412887952) * @version v.0.1 */ @Entity public class SysRole implements Serializable { private static final long serialVersionUID = 1L; @Id@GeneratedValue private Long id;//编号 private String role; // 角色标识程序中判断使用,如"admin",这个是唯一的: private String description; // 角色描述,UI界面显示使用 private Boolean available = Boolean.FALSE; // 是否可用,如果不可用将不会添加给用户 //角色 -- 权限关系:多对多关系; @ManyToMany(fetch=FetchType.EAGER) @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="permissionId")}) private List<SysPermission> permissions; // 用户 - 角色关系定义; @ManyToMany @JoinTable(name="SysUserRole",joinColumns={@JoinColumn(name="roleId")},inverseJoinColumns={@JoinColumn(name="uid")}) private List<UserInfo> userInfos;// 一个角色对应多个用户 //getter and setter //toString }
SysPermission:
package com.slf.firstappdemo.framework.shiro.domain; import java.io.Serializable; import java.util.List; import javax.persistence.*; /** * 权限实体类; * @author Angel(QQ:412887952) * @version v.0.1 */ @Entity public class SysPermission implements Serializable{ private static final long serialVersionUID = 1L; @Id@GeneratedValue private long id;//主键. private String name;//名称. @Column(columnDefinition="enum('menu','button')") private String resourceType;//资源类型,[menu|button] private String url;//资源路径. private String permission; //权限字符串,menu例子:role:*,button例子:role:create,role:update,role:delete,role:view private Long parentId; //父编号 private String parentIds; //父编号列表 private Boolean available = Boolean.FALSE; @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name="SysRolePermission",joinColumns={@JoinColumn(name="permissionId")},inverseJoinColumns={@JoinColumn(name="roleId")}) private List<SysRole> roles; //getter and setter //toString() }
UserInfoRepository:
package com.slf.firstappdemo.framework.shiro.repository; import com.slf.firstappdemo.framework.shiro.domain.UserInfo; import org.springframework.data.repository.CrudRepository; /** *@Author:Flm *@Description:UserInfo持久化类 *@Date:17:14 2018/3/13 */ public interface UserInfoRepository extends CrudRepository<UserInfo,Long> { //通过username查找用户信息 public UserInfo findByUsername(String username); }
UserInfoServiceImpl:
import com.slf.firstappdemo.framework.shiro.domain.UserInfo; import com.slf.firstappdemo.framework.shiro.repository.UserInfoRepository; import com.slf.firstappdemo.framework.shiro.service.UserInfoService; import org.springframework.stereotype.Service; import javax.annotation.Resource; @Service public class UserInfoServiceImpl implements UserInfoService{ @Resource private UserInfoRepository userInfoRepository; @Override public UserInfo findByUserName(String username) { System.out.println("UserInfoServiceImpl.findByUserName()"); return userInfoRepository.findByUsername(username); } }
接口省略。
controller:
package com.slf.firstappdemo.framework.shiro.controller; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authz.annotation.RequiresPermissions; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import javax.servlet.http.HttpServletRequest; import java.util.Map; @Controller @RequestMapping("/shiro") public class ShiroController { @RequestMapping({"/","/index"}) public String index(){ return"/framwork/shiro/index"; } @RequestMapping(value="/login",method= RequestMethod.GET)//跳转页面 public String login(){ return"/framwork/shiro/login"; } @RequestMapping(value="/login",method= RequestMethod.POST)//处理登录 public String doLogin(HttpServletRequest request, Map<String,Object> map){ System.out.println("ShiroController.doLogin()"); //登录失败从request中获取shiro处理的异常信息 //shiroLoginFailure:就是shiro异常类的全名 String exception = (String)request.getAttribute("shiroLoginFailure"); System.out.println("exception========="+exception); String msg = ""; if (exception!=null){ if (UnknownAccountException.class.getName().equals(exception)){ System.out.println("UnknownAccountException -- > 账号不存在:"); msg = "UnknownAccountException -- > 账号不存在:"; }else if (IncorrectCredentialsException.class.getName().equals(exception)) { System.out.println("IncorrectCredentialsException -- > 密码不正确:"); msg = "IncorrectCredentialsException -- > 密码不正确:"; } else if ("kaptchaValidateFailed".equals(exception)) { System.out.println("kaptchaValidateFailed -- > 验证码错误"); msg = "kaptchaValidateFailed -- > 验证码错误"; } else { msg = "else >> "+exception; System.out.println("else -- >" + exception); } } map.put("msg",msg); return "/framwork/shiro/login"; } @RequestMapping("/userList") @RequiresPermissions("userInfo:view") public String userInfo(){ return "/framwork/shiro/userinfo"; } @RequestMapping("/userAdd") @RequiresPermissions("userInfo:add") public String userAdd(){ return "/framwork/shiro/userinfoAdd"; } }
login.html(其他页面省略):
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>login</title> </head> <body> 错误信息:<h4 th:text="${msg}"></h4> <form action="/shiro/login" method="post"> <p>账号:<input type="text" name="username" value="admin"/></p> <p>密码:<input type="text" name="password" value="123456"/></p> <p><input type="submit" value="登录"/></p> </form> </body> </html>
自定义实现Realm:
此类为身份验证的核心类,需要继承AuthorizingRealm类,并且实现两个方法
(1)doGetAuthenticationInfo(AuthenticationToken authenticationToken) 获取认证信息,用来验证身份信息。
(2)doGetAuthorizationInfo(PrincipalCollection principalCollection) 获取授权信息,用来进行权限验证。
package com.slf.firstappdemo.framework.shiro.realm; import com.slf.firstappdemo.framework.shiro.domain.SysPermission; import com.slf.firstappdemo.framework.shiro.domain.SysRole; import com.slf.firstappdemo.framework.shiro.domain.UserInfo; import com.slf.firstappdemo.framework.shiro.service.UserInfoService; 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.apache.shiro.util.ByteSource; import javax.annotation.Resource; /** * @Author:Flm * @Description:身份验证核心类 * @Date:17:21 2018/3/13 */ public class MyShiroRealm extends AuthorizingRealm { @Resource private UserInfoService userInfoService; /** * @Author:Flm * @Description:认证信息(身份验证) * @Date:17:23 2018/3/13 */ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("MyShiroRealm.doGetAuthenticationInfo()"); //获取用户的输入账号 String username = (String) authenticationToken.getPrincipal(); System.out.println(authenticationToken.getCredentials()); //通过username从数据库中查找user对象 //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro也是有时间间隔机制,2分钟内不会重复执行该方法 UserInfo userInfo = userInfoService.findByUserName(username); System.out.println("----->>userInfo=" + userInfo); if (userInfo == null) { return null; } /** * 获取权限信息:这里没有进行实现 * 请自行根据UserInfo,Role,Permission进行实现 * 获取之后可以在前端for循环显示所有链接 */ //userInfo.setPermissions(userService.findPermissions(user)); //账号判断 //加密方式 //交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,如果觉得人家的不好可以自定义实现 SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo,//用户名 userInfo.getPassword(),//密码 ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt getName()//realm name ); //明文,若存在,则将此用户存放到登录认证info中,无需自己做密码对比,shiro会为我们进行密码对比校验 //SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(userInfo,userInfo.getPassword(),getName()); return authenticationInfo; } /** * 此方法调用 hasRole,hasPermission的时候才会进行回调. * * 权限信息.(授权): * 1、如果用户正常退出,缓存自动清空; * 2、如果用户非正常退出,缓存自动清空; * 3、如果我们修改了用户的权限,而用户不退出系统,修改的权限无法立即生效。 * (需要手动编程进行实现;放在service进行调用) * 在权限修改后调用realm中的方法,realm已经由spring管理,所以从spring中获取realm实例, * 调用clearCached方法; * :Authorization 是授权访问控制,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等。 * @param principalCollection * @return */ @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { /* * 当没有使用缓存的时候,不断刷新页面的话,这个代码会不断执行, * 当其实没有必要每次都重新设置权限信息,所以我们需要放到缓存中进行管理; * 当放到缓存中时,这样的话,doGetAuthorizationInfo就只会执行一次了, * 缓存过期之后会再次执行。 */ System.out.println("权限配置-----》MyShiroRealm.doGetAuthorizationInfo()"); SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo(); UserInfo userInfo = (UserInfo)principalCollection.getPrimaryPrincipal(); //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 // UserInfo userInfo = userInfoService.findByUsername(username) //权限单个添加; // 或者按下面这样添加 //添加一个角色,不是配置意义上的添加,而是证明该用户拥有admin角色 // authorizationInfo.addRole("admin"); //添加权限 // authorizationInfo.addStringPermission("userInfo:query"); //在认证成功之后返回. //设置角色信息. //支持 Set集合, //用户的角色对应的所有权限,如果只使用角色定义访问权限,下面的四行可以不要 // List<Role> roleList=user.getRoleList(); // for (Role role : roleList) { // info.addStringPermissions(role.getPermissionsName()); // } for(SysRole role:userInfo.getRoleList()){ simpleAuthorizationInfo.addRole(role.getRole()); for(SysPermission p:role.getPermissions()){ simpleAuthorizationInfo.addStringPermission(p.getPermission()); System.out.println("user拥有权限:"+p.getPermission()); } } return simpleAuthorizationInfo; } }
实现了自定义的Realm之后,就需要将shiro配置到spring中:
package com.slf.firstappdemo.framework.shiro.config; import com.slf.firstappdemo.framework.shiro.realm.MyShiroRealm; import org.apache.shiro.authc.credential.HashedCredentialsMatcher; 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.DefaultWebSecurityManager; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; /** * @author Angel(QQ:412887952) * @version v.0.1 * @Author:Flm * @Description:shiro config * @Date:16:15 2018/3/13 * Shiro 配置 * * Apache Shiro 核心通过 Filter 来实现,就好像SpringMvc 通过DispachServlet 来主控制一样。 * 既然是使用 Filter 一般也就能猜到,是通过URL规则来进行过滤和权限校验,所以我们需要定义一系列关于URL的规则和访问权限。 */ @Configuration public class ShiroConfig { /** * 凭证匹配器 * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了 * 所以我们需要修改下doGetAuthenticationInfo中的代码; * ) * * @return */ @Bean public HashedCredentialsMatcher hashedCredentialsMatcher() { HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法; hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5("")); return hashedCredentialsMatcher; } /** * 身份认证realm; * (这个需要自己写,账号密码校验;权限等) * * @return */ @Bean public MyShiroRealm myShiroRealm() { MyShiroRealm myShiroRealm = new MyShiroRealm(); myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return myShiroRealm; } @Bean public SecurityManager securityManager() { DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); //设置realm securityManager.setRealm(myShiroRealm()); return securityManager; } /** * ShiroFilterFactoryBean 处理拦截资源文件问题。 * 注意:单独一个ShiroFilterFactoryBean配置是或报错的,因为在 * 初始化ShiroFilterFactoryBean的时候需要注入:SecurityManager * <p> * Filter Chain定义说明 * 1、一个URL可以配置多个Filter,使用逗号分隔 * 2、当设置多个过滤器时,全部验证通过,才视为通过 * 3、部分过滤器可指定参数,如perms,roles */ @Bean public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) { System.out.println("ShiroConfiguration.shiroFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); //必须设置securityManager shiroFilterFactoryBean.setSecurityManager(securityManager); //拦截器 Map<String, String> filterChainDefinitioinMap = new LinkedHashMap<String, String>(); //配置退出过滤器,其中的具体的退出代码shiro已经实现 filterChainDefinitioinMap.put("/logout", "logout"); //过滤连定义,从上向下顺序执行,一般将/**放在最为下边 !!!!! //authc:所有的url都必须认证通过才可以访问;anon:所有的url都可以匿名访问 filterChainDefinitioinMap.put("/shiro/**", "authc"); //如果不设置默认会自动寻找web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/shiro/login");//此处应是url并非静态资源位置 //登陆成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/shiro/index"); //未授权页面 shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitioinMap); return shiroFilterFactoryBean; } /** * 开启shiro aop注解支持. * 使用代理方式;所以需要开启代码支持; * * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } }
配置完成后可以顺利使用。
总结一下springboot整合shiro的流程:
(1)添加对应依赖
(2)添加shiro验证过程中所需要的bean、方法、持久层信息。
(3)重新自定义的继承自AuthorizingRealm的Realm。
- AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)认证方法流程
- 从token中取出用户信息。
- 根据获得的用户信息从数据库中查找user对象。
- 进行判断,若无此用户,则返回null,有的话则将其交给AuthenticatingRealm使用CredentialsMatcher进行密码匹配,密码匹配的方式可以自定义实现。。
- 返回authenticatingRealm。
- AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection)权限验证方法流程
- 从principalCollection中获取用户信息。
- 根据用户信息获取权限信息,将权限信息放入authorizationInfo中(shiro会自动进行权限验证管理)。
- 返回authorizationInfo。
(4)将shiro配置到项目中。
- 由于我们对密码进行了加密,所以需要创建凭证匹配器CredentialsMatcher,用来匹配授权认证的时候的密码。
- 创建realm并将凭证匹配器CredentialsMatcher加入到realm中。
- 创建SecurityManager并将realm设到其中
- 创建ShiroFilterFactoryBean,并将SecurityManager注入其中。
- 创建AuthorizationAttributeSourceAdvisor 开启AOP注解支持
至此,shiro已经简单的配置完毕。
后续发现问题会再次修改。
详细步骤请见:http://412887952-qq-com.iteye.com/blog/2299732