1、添加相关的依赖,spring-boot-starter-data-jpa在 IEDA中创建SpringBoot2.0项目-超详细(一)博客中已经添加,只需添加以下依赖即可:
<!--shiro相关--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>net.sourceforge.nekohtml</groupId> <artifactId>nekohtml</artifactId> <version>1.9.22</version> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency>
2、在application.properties文件中添加:
#shiro相关 spring.jpa.database=mysql spring.jpa.show-sql=true spring.jpa.hibernate.ddl-auto=update spring.jpa.ddl-auto=update spring.jpa.naming.strategy=org.hibernate.cfg.DefaultComponentSafeNamingStrategy spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect spring.thymeleaf.cache=false spring.thymeleaf.mode=LEGACYHTML5
如果是yml文件添加:
spring: jpa: database: mysql show-sql: true hibernate: ddl-auto: update naming: strategy: org.hibernate.cfg.DefaultComponentSafeNamingStrategy properties: hibernate: dialect: org.hibernate.dialect.MySQL5Dialect thymeleaf: cache: false mode: LEGACYHTML5
3、添加相关的实体类:
用户信息UserInfo:
@Data @ToString @Entity public class UserInfo implements Serializable { @Id @GeneratedValue private Integer uid; @Column(unique = true) private String username;//帐号 private String name;//名称(昵称或者真实姓名,不同系统不同定义) private String password; //密码; private String salt;//加密密码的盐 private byte state;//用户状态,0:创建未认证(比如没有激活,没有输入验证码等等)--等待验证的用户 , 1:正常状态,2:用户被锁定. @ManyToMany(fetch = FetchType.EAGER)//立即从数据库中进行加载数据; @JoinTable(name = "SysUserRole", joinColumns = {@JoinColumn(name = "uid")}, inverseJoinColumns = {@JoinColumn(name = "roleId")}) private List<SysRole> roleList;// 一个用户具有多个角色 }
角色信息SysRole:
@Entity public class SysRole implements Serializable { @Id @GeneratedValue private Integer 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;// 一个角色对应多个用户 //此处是set和get方法,可以利用编译器去自动生成 }
权限信息SysPermission:
@Entity public class SysPermission implements Serializable { @Id @GeneratedValue private Integer 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 @JoinTable(name = "SysRolePermission", joinColumns = {@JoinColumn(name = "permissionId")}, inverseJoinColumns = {@JoinColumn(name = "roleId")}) private List<SysRole> roles; //此处是set和get方法,可以利用编译器去自动生成 }
此处有个问题,就是只有UserInfo类可以使用lombok表达式,其他两个类不能使用,只能自己手动去生成set/get方法,此处报了以下错误,博主搞了半天一直报以下错误:
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.xhy.xczx.pojo.SysRole.userInfos, could not initialize proxy - no Session
由于能力有限没能解决,有解决的可以指正,所以这两个实体类未能使用lombok表达式,只能手动写的set/get方法,然后启动下项目,无报错信息后即可在数据库中自动生成对应的几张表:
然后插入需要的数据:
INSERT INTO `user_info` (`uid`,`username`,`name`,`password`,`salt`,`state`) VALUES ('1', 'admin', '管理员', 'd3c59d25033dbf980d29554025c23a75', '8d78869f470951332959580424d4bf4f', 0); INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (1,0,'用户管理',0,'0/','userInfo:view','menu','userInfo/userList'); INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (2,0,'用户添加',1,'0/1','userInfo:add','button','userInfo/userAdd'); INSERT INTO `sys_permission` (`id`,`available`,`name`,`parent_id`,`parent_ids`,`permission`,`resource_type`,`url`) VALUES (3,0,'用户删除',1,'0/1','userInfo:del','button','userInfo/userDel'); INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (1,0,'管理员','admin'); INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (2,0,'VIP会员','vip'); INSERT INTO `sys_role` (`id`,`available`,`description`,`role`) VALUES (3,1,'test','test'); INSERT INTO `sys_role_permission` VALUES ('1', '1'); INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (1,1); INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (2,1); INSERT INTO `sys_role_permission` (`permission_id`,`role_id`) VALUES (3,2); INSERT INTO `sys_user_role` (`role_id`,`uid`) VALUES (1,1);
然后在resources/templates下创建以下html文件:
为了避免命名上的问题,特意贴出各个页面名字:
403.html index.html login.html userInfo.html userInfoAdd.html userInfoDel.html
以下只有login.html页面有具体内容,fkHtml.ftl属于以前Freemarker教程中的页面,不用新增,其他页面根据自己需求去写:
login.html: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Login</title> </head> <body> 错误信息:<h4 th:text="${msg}"></h4> <form action="" 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>
现在贴出Shiro的两个核心类:
ShiroConfig.java:
package com.xhy.xczx.shiro; 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 org.springframework.web.servlet.handler.SimpleMappingExceptionResolver; import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; @Configuration public class ShiroConfig { @Bean public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager) { System.out.println("ShiroConfiguration.shirFilter()"); ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(securityManager); //拦截器. Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>(); // 配置不会被拦截的链接 顺序判断 filterChainDefinitionMap.put("/static/**", "anon"); //配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了 filterChainDefinitionMap.put("/logout", "logout"); //<!-- 过滤链定义,从上向下顺序执行,一般将/**放在最为下边 -->:这是一个坑呢,一不小心代码就不好使了; //<!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问--> filterChainDefinitionMap.put("/**", "authc"); // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面 shiroFilterFactoryBean.setLoginUrl("/login"); // 登录成功后要跳转的链接 shiroFilterFactoryBean.setSuccessUrl("/index"); //未授权界面; shiroFilterFactoryBean.setUnauthorizedUrl("/403"); shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap); return shiroFilterFactoryBean; } /** * 凭证匹配器 * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了 * ) * @return */ @Bean public HashedCredentialsMatcher hashedCredentialsMatcher(){ HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法; hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5("")); return hashedCredentialsMatcher; } @Bean public MyShiroRealm myShiroRealm(){ MyShiroRealm myShiroRealm = new MyShiroRealm(); myShiroRealm.setCredentialsMatcher(hashedCredentialsMatcher()); return myShiroRealm; } @Bean public SecurityManager securityManager(){ DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(); securityManager.setRealm(myShiroRealm()); return securityManager; } /** * 开启shiro aop注解支持. * 使用代理方式;所以需要开启代码支持; * @param securityManager * @return */ @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){ AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor(); authorizationAttributeSourceAdvisor.setSecurityManager(securityManager); return authorizationAttributeSourceAdvisor; } @Bean(name="simpleMappingExceptionResolver") public SimpleMappingExceptionResolver createSimpleMappingExceptionResolver() { SimpleMappingExceptionResolver r = new SimpleMappingExceptionResolver(); Properties mappings = new Properties(); mappings.setProperty("DatabaseException", "databaseError");//数据库异常处理 mappings.setProperty("UnauthorizedException","403"); r.setExceptionMappings(mappings); // None by default r.setDefaultErrorView("error"); // No default r.setExceptionAttribute("ex"); // Default is "exception" //r.setWarnLogCategory("example.MvcLogger"); // No default return r; } }
MyShiroRealm.java:
package com.xhy.xczx.shiro; import com.xhy.xczx.pojo.SysPermission; import com.xhy.xczx.pojo.SysRole; import com.xhy.xczx.pojo.UserInfo; import com.xhy.xczx.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; public class MyShiroRealm extends AuthorizingRealm { @Resource private UserInfoService userInfoService; @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) { System.out.println("权限配置-->MyShiroRealm.doGetAuthorizationInfo()"); SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo(); UserInfo userInfo = (UserInfo)principals.getPrimaryPrincipal(); for(SysRole role:userInfo.getRoleList()){ authorizationInfo.addRole(role.getRole()); for(SysPermission p:role.getPermissions()){ authorizationInfo.addStringPermission(p.getPermission()); } } return authorizationInfo; } /*主要是用来进行身份认证的,也就是说验证用户输入的账号和密码是否正确。*/ @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { System.out.println("MyShiroRealm.doGetAuthenticationInfo()"); //获取用户的输入的账号. String username = (String)token.getPrincipal(); System.out.println(token.getCredentials()); //通过username从数据库中查找 User对象,如果找到,没找到. //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法 UserInfo userInfo = userInfoService.findByUsername(username); System.out.println("----->>userInfo="+userInfo); if(userInfo == null){ return null; } SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo( userInfo, //用户名 userInfo.getPassword(), //密码 ByteSource.Util.bytes(userInfo.getCredentialsSalt()),//salt=username+salt getName() //realm name ); return authenticationInfo; } }
然后编写dao、service和Controller中内容:
UserInfoDao: public interface UserInfoDao extends CrudRepository<UserInfo,Long> { UserInfo findByUsername(String username); }
UserInfoService:
public interface UserInfoService { /**通过username查找用户信息;*/ UserInfo findByUsername(String username); }
UserInfoServiceImpl:
@Service public class UserInfoServiceImpl implements UserInfoService { @Resource private UserInfoDao userInfoDao; @Override public UserInfo findByUsername(String username) { System.out.println("UserInfoServiceImpl.findByUsername()"); return userInfoDao.findByUsername(username); } }
UserInfoController:
@Controller @RequestMapping("/userInfo") public class UserInfoController { /** * 用户查询. * @return */ @RequestMapping("/userList") @RequiresPermissions("userInfo:view")//权限管理; public String userInfo(){ return "userInfo"; } /** * 用户添加; * @return */ @RequestMapping("/userAdd") @RequiresPermissions("userInfo:add")//权限管理; public String userInfoAdd(){ return "userInfoAdd"; } /** * 用户删除; * @return */ @RequestMapping("/userDel") @RequiresPermissions("userInfo:del")//权限管理; public String userDel(){ return "userInfoDel"; } } 核心跳转类HomeController: @Controller public class HomeController { @RequestMapping({"/","/index"}) public String index(){ return"/index"; } @RequestMapping("/login") public String login(HttpServletRequest request, Map<String, Object> map) throws Exception{ System.out.println("HomeController.login()"); // 登录失败从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); // 此方法不处理登录成功,由shiro进行处理 return "/login"; } @RequestMapping("/403") public String unauthorizedRole(){ System.out.println("------没有权限-------"); return "403"; } }
最后进行测试:
1、编写好后就可以启动程序,访问http://localhost:8081页面进入首页,由于没有登录就会跳转到http://localhost:8081/login页面。登录之后就会跳转到index页面,登录后,直接在浏览器中输入http://localhost:8081/userInfo/userList访问就会看到用户信息。http://localhost:8081/logout页面,退出登录,上面这些操作时候触发MyShiroRealm.doGetAuthenticationInfo()这个方法,也就是登录认证的方法。
2、登录admin账户,访问:http://127.0.0.1:8081/userInfo/userAdd显示用户添加界面,访问http://127.0.0.1:8081/userInfo/userDel显示403没有权限。上面这些操作时候触发MyShiroRealm.doGetAuthorizationInfo()这个方面,也就是权限校验的方法。
3、修改admin不同的权限进行测试。
测试结果如下:
隐含内容:自动生成数据库表结构
在配置文件里配置下面内容,在数据库中创建数据库,启动项目可以自动生成对应的表和表结构。
spring.jpa.hibernate.ddl-auto=update spring.jpa.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
控制台测试结果:
数据库: