1、导入依赖
<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> <exclusions> <exclusion> <groupId>org.junit.vintage</groupId> <artifactId>junit-vintage-engine</artifactId> </exclusion> </exclusions> </dependency> <!-- MyBatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.0</version> </dependency> <!-- mysql驱动 依赖 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.25</version> <scope>runtime</scope> </dependency> <!--Druid--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.10</version> </dependency> <!--shiro--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency> <!--shiro标签 xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro" --> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> </dependencies>
2、书写配置类
@Configuration public class ShiroConfig { @Bean public ShiroDialect getShiroDialect() { return new ShiroDialect(); } @Bean//realm public MyRealm getMyRealm(DataSource dataSource) { MyRealm myRealm = new MyRealm(); return myRealm; } @Bean//安全管理器 public DefaultWebSecurityManager getDefaultWebSecurityManager(MyRealm myRealm) { DefaultWebSecurityManager defaultSecurityManager = new DefaultWebSecurityManager(); defaultSecurityManager.setRealm(myRealm);//SecurityManager完成校验需要realm return defaultSecurityManager; } @Bean//过滤器 public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultSecurityManager securityManager) { ShiroFilterFactoryBean filter = new ShiroFilterFactoryBean(); //过滤器是shiro执行权限的核心,进行认证和授权是需要SecurityManager的 filter.setSecurityManager(securityManager); //设置shiro的拦截规则 Map<String, String> filterMap = new HashMap<>(); //user:使用remberme的用户可访问 //perms:对应权限可访问 //role:对应的角色才能访问 filterMap.put("/", "anon");//anon表示不拦截(匿名用户可访问) filterMap.put("/login.html", "anon"); filterMap.put("/regist.html", "anon"); filterMap.put("/user/login", "anon"); filterMap.put("/user/regist", "anon"); filterMap.put("/static/**", "anon"); filterMap.put("/index.html", "anon"); filterMap.put("/**", "authc");//authc表示认证用户可访问 filter.setFilterChainDefinitionMap(filterMap); filter.setLoginUrl("/login.html"); //设置未授权访问的页面 filter.setUnauthorizedUrl("/login.html"); return filter; } }
配置类中的Realm需要自定义,在使用ini文件作为数据源的时候使用的是lniRealm,使用数据库作为数据源的时候使用的是JdbcRealm,JdbcRealm中的数据库的表名称与表的字段名称都是固定的。在使用自定义Realm之后数据库不是固定不变的,但是dao层需要我们自己书写代码来完成实现,配置类中的Realm也需要我们自己定义。
3、自定义Realm
public class MyRealm extends AuthorizingRealm {//实现了接口的类才是一个realm类 @Autowired private UserDao userDao; @Autowired private RoleDao roleDao; @Autowired private PermissionDao permissionDao; public String getName() { return "myRealm"; } @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { //获取当前认证的用户的用户名,跟doGetAuthenticationInfo方法中的new SimpleAuthenticationInfo参数是对应的,获取到的数据与认证的时候封装的数据是对应的 String username= (String) principalCollection.iterator().next(); //根据用户名查询当前用户的前台列表,Set集合能够去除重复的数据 Set<String> roleNames=roleDao.queryRoleNameByUsername(username); Set<String> ps=permissionDao.queryPermissionsByUsername(username); SimpleAuthorizationInfo info=new SimpleAuthorizationInfo(); info.setRoles(roleNames); info.setStringPermissions(ps); return info; } @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { //参数authenticationToken就是传递的subject.login(token)中的参数 UsernamePasswordToken token= (UsernamePasswordToken) authenticationToken; //从token中获取用户名 String username=token.getUsername();
//根据用户名查询用户的安全数据(数据库中的用户数据) User user= userDao.getUserByUsername(username); if(user==null){ return null; } AuthenticationInfo info=new SimpleAuthenticationInfo(//将密码封装为shiro需要的格式 username,//当前用户用户名,跟上面的doGetAuthorizationInfo方法是对应的 user.getUserPwd(),//从数据库查询出来的安全密码 getName()); return info; } }
- 创建一个类并继承AuthorizingRealm类
- 重写doGetAuthorizationInfo获取授权信息和doGetAuthenticationInfo方法来获取认证信息
- 重写getName方法返回当前realm的一个自定义名称
4、controller层
(1)页面跳转所需要的controller
@Controller public class PageController { @RequestMapping("/login.html") public String login(){ return "login"; } @RequestMapping("/") public String login1(){ return "login"; } @RequestMapping("/index.html") public String index(){ return "index"; } }
(2)用户登录的controller
@Controller @RequestMapping("user") public class UserController { @Autowired private UserService userService; @RequestMapping("login") public String login(String username,String password){ try{ userService.checkLogin(username,password); System.out.println("成功"); System.out.println(username+password); return "index"; }catch (Exception e){ e.printStackTrace(); System.out.println("失败"); System.out.println(username+password); return "login"; } } }
5、service层
@Service public class UserService { public void checkLogin(String username,String password) throws Exception{ Subject subject= SecurityUtils.getSubject(); UsernamePasswordToken token=new UsernamePasswordToken(username,password); subject.login(token); } }
完成用户的认证
6、dao层
用户:
@Repository public interface UserDao { User getUserByUsername(String username);//用户名获取用户 }
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zhb.dao.UserDao"> <resultMap id="userMap" type="User"> <id column="user_id" property="userId"></id> <result column="username" property="userName"></result> <result column="password" property="userPwd"></result> <result column="password_salt" property="pwdSalt"></result> </resultMap> <select id="getUserByUsername" resultMap="userMap"> select * from tb_users where username=#{username} </select> </mapper>
角色:
@Repository public interface RoleDao { public Set<String> queryRoleNameByUsername(String username);//用户名获取角色 }
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zhb.dao.RoleDao"> <select id="queryRoleNameByUsername" resultType="String" resultSets="java.util.Set"> SELECT role_name FROM tb_users INNER JOIN tb_urs ON tb_users.user_id = tb_urs.uid INNER JOIN tb_roles ON tb_urs.rid = tb_roles.role_id WHERE tb_users.username=#{username} </select> </mapper>
权限:
@Repository public interface PermissionDao { public Set<String> queryPermissionsByUsername(String username); }
<?xml version="1.0" encoding="utf-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.zhb.dao.PermissionDao"> <select id="queryPermissionsByUsername" resultType="String" resultSets="java.util.Set"> SELECT tb_permissions.permission_code FROM tb_users INNER JOIN tb_urs ON tb_users.user_id = tb_urs.uid INNER JOIN tb_roles ON tb_urs.rid = tb_roles.role_id INNER JOIN tb_rps ON tb_roles.role_id = tb_rps.rid INNER JOIN tb_permissions ON tb_rps.pid = tb_permissions.permission_id WHERE tb_users.username=#{username} </select> </mapper>
7、bean
@Data public class User { private Integer userId; private String userName; private String userPwd; private String pwdSalt; }
8、yml配置文件
spring: datasource: druid: username: root password: root driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/myshiro?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8 initialSize: 5 minIdle: 5 maxActive: 20 maxWait: 60000 timeBetweenEvictionRunsMillis: 60000 minEvictableIdleTimeMillis: 300000 validationQuery: SELECT 1 FROM DUAL testWhileIdle: true testOnBorrow: false testOnReturn: false poolPreparedStatements: true # mybatis配置 mybatis: mapper-locations: classpath:mappers/*.xml # mapper映射文件位置 type-aliases-package: com.zhb.beans # 实体类所在的位置 configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #用于控制台打印sql语句
配置数据源与mybatis,其中mybatis配置xml文件的位置、实体类位置、以及控制台打印SQL语句
9、页面
登录页:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>login</title> </head> <body> <form action="/user/login"> <input type="text" name="username"> <input type="password" name="password"> <input type="submit" value="提交"> </form> </body> </html>
主页:
<!DOCTYPE html> <html lang="en" xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro"> <head> <meta charset="UTF-8"> <title>index</title> </head> <body> <h3>index</h3> <hr/> <shiro:guest> 欢迎游客访问<a href="login.html">登录</a> </shiro:guest> <shiro:user> 用户[ <shiro:principal/> ]登录成功 当前用户的身份 <shiro:hasRole name="管理员">管理员</shiro:hasRole> <shiro:hasRole name="学生">学生</shiro:hasRole> <hr/> 学生: <ul> <shiro:hasPermission name="stu_select"> <li><a href="#">查询</a></li> </shiro:hasPermission> </ul> <hr/> 教师: <ul> <shiro:hasPermission name="tea_select"> <li><a href="#">查询</a></li> </shiro:hasPermission> </ul> <ul> <shiro:hasPermission name="tea_delete"> <li><a href="#">删除</a></li> </shiro:hasPermission> </ul> <ul> <shiro:hasPermission name="tea_update"> <li><a href="#">修改</a></li> </shiro:hasPermission> </ul> 管理员: <ul> <shiro:hasPermission name="man_select"> <li><a href="#">查询</a></li> </shiro:hasPermission> </ul> <ul> <shiro:hasPermission name="man_delete"> <li><a href="#">删除</a></li> </shiro:hasPermission> </ul> <ul> <shiro:hasPermission name="man_update"> <li><a href="#">修改</a></li> </shiro:hasPermission> </ul> <ul> <shiro:hasPermission name="man_add"> <li><a href="#">添加</a></li> </shiro:hasPermission> </ul> </shiro:user> </body> </html>
11、数据库设计(五张表)
(1)用户表
(2)用户角色表
(3)角色表
(4)角色权限表
(5)权限
这些表都是自定义的,不是shiro默认的表,字段也不局限于shiro默认的表的字段,因为dao层是我们自己实现的。
11、测试
(1)管理员
(2)学生
(3)教师