springboot 整合 shiro (使用了 thymeleaf模板引擎)
Posted alida
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot 整合 shiro (使用了 thymeleaf模板引擎)相关的知识,希望对你有一定的参考价值。
数据库结构
1. 项目目录结构
2. pom.xml 添加依赖
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.2.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>cn.xej</groupId> <artifactId>springboot-shiro</artifactId> <version>0.0.1-SNAPSHOT</version> <name>springboot-shiro</name> <description>Demo project for Spring Boot</description> <properties> <java.version>1.8</java.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.1</version> </dependency> <dependency> <groupId>com.github.theborakompanioni</groupId> <artifactId>thymeleaf-extras-shiro</artifactId> <version>2.0.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <scope>runtime</scope> <optional>true</optional> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.13</version> </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> </dependencies> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins> </build> </project>
3. application.yml 配置文件
spring: datasource: url: jdbc:mysql://localhost:3306/tb_shiro?useUnicode=true&&characterEncoding=utf-8&serverTimezone=GMT username: root password: 123456 type: com.alibaba.druid.pool.DruidDataSource thymeleaf: cache: false mybatis: mapper-locations: classpath:mapping/*.xml type-aliases-package: cn.xej.pojo configuration: map-underscore-to-camel-case: true # 该配置就是将带有下划线的表字段映射为驼峰格式的实体类属性
4. User 实体类
package cn.xej.pojo; import lombok.Data; @Data public class User { private String userId; private String password; private String name; }
5. UserDao接口
package cn.xej.mapper; import cn.xej.pojo.User; import org.springframework.stereotype.Repository; import java.util.List; @Repository public interface UserDao { public User findByUserId(String userId); public List<String> queryRolesIdByUserId(String userId); }
6. UserDao.xml文件
<?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="cn.xej.mapper.UserDao"> <select id="findByUserId" resultType="User"> SELECT * FROM tb_user WHERE user_id=#{userId} </select> <select id="queryRolesIdByUserId" resultType="String"> SELECT ur.role_id FROM tb_user AS u,tb_user_role AS ur WHERE u.user_id = ur.user_id AND u.user_id = #{userId} </select> </mapper>
7. UserService接口
package cn.xej.service; import cn.xej.pojo.User; import java.util.List; public interface UserService { // 根据用户id查询该用户 public User findByUserId(String userId); // 根据用户id获取该用户角色 public List<String> getRolesIdByUserId(String userId); }
8. UserServiceimpl接口实现类
package cn.xej.service.impl; import cn.xej.mapper.UserDao; import cn.xej.pojo.User; import cn.xej.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; @Service public class UserServiceimpl implements UserService { @Autowired private UserDao userDao; @Override public User findByUserId(String userId) { return userDao.findByUserId(userId); } @Override public List<String> getRolesIdByUserId(String userId) { return userDao.queryRolesIdByUserId(userId); } }
9. SpringbootShiroApplication 启动类添加包扫描注解
package cn.xej; import org.mybatis.spring.annotation.MapperScan; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication @MapperScan("cn.xej.mapper") // 配置一个或多个包路径,自动的扫描这些包路径下的类,自动的为它们生成代理类。 public class SpringbootShiroApplication { public static void main(String[] args) { SpringApplication.run(SpringbootShiroApplication.class, args); } }
10. RespObj
package cn.xej.common; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor public class RespObj { private Integer code; private String message; private Object data; public static RespObj build(Integer code,String message,Object data){ return new RespObj(code,message,data); } }
11. SysController(路由跳转控制器)
package cn.xej.controller; import org.apache.shiro.SecurityUtils; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class SysController { // 进入到登录页面 @RequestMapping({"/","/welcome"}) public String welcome(){ return "login"; } // 进入到首页(登录成功或游客) @RequestMapping("/index") public String index(){ return "index"; } }
12. UserController(控制器)
package cn.xej.controller; import cn.xej.common.RespObj; import cn.xej.pojo.User; import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.AuthenticationException; import org.apache.shiro.authc.IncorrectCredentialsException; import org.apache.shiro.authc.UnknownAccountException; import org.apache.shiro.authc.UsernamePasswordToken; import org.apache.shiro.subject.Subject; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpSession; @Controller @RequestMapping("/user") public class UserController { // 登录用户, 返回json数据,因此要加 @ResponseBody注解 @PostMapping("/toLogin") @ResponseBody public RespObj toLogin(String userId, String password, HttpSession session){ // 1. 获取 主体 subject Subject subject = SecurityUtils.getSubject();
// 2. 将账号和密码进行封装 UsernamePasswordToken token = new UsernamePasswordToken(userId,password); // 3. shiro认证,进入自定义UserRealm中 try { subject.login(token);
// 认证成功,将用户名存到session中,返回json数据 session.setAttribute("currentUserName",((User)subject.getPrincipal()).getName()); return RespObj.build(200,"ok",null); } catch (Exception e) { System.out.println("账号或密码错误"); return RespObj.build(500,"账号或密码错误",null); } }
// 注销用户 @PostMapping("/logout") @ResponseBody public RespObj logout(){ Subject subject = SecurityUtils.getSubject(); subject.logout(); return RespObj.build(200,"ok",null); } @RequestMapping("/add") public String add(){ return "pages/add"; } @RequestMapping("/update") public String update(){ return "pages/update"; } }
13. 自定义UserRealm
package cn.xej.config; import cn.xej.pojo.User; import cn.xej.service.UserService; import org.apache.shiro.authc.*; 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 java.util.List; public class UserRealm extends AuthorizingRealm { @Autowired private UserService userService; // 授权 @Override protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) { System.out.println("授权"); SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(); User user = (User) principalCollection.getPrimaryPrincipal(); List<String> roles = userService.getRolesIdByUserId(user.getUserId()); info.addRoles(roles); return info; } // 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("认证"); UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken; User user = userService.findByUserId(token.getUsername()); if(user==null){ return null; } return new SimpleAuthenticationInfo(user,user.getPassword(),getName());
// 第一个参数,将会传到授权中进行获取,比如 User user = (User) principalCollection.getPrimaryPrincipal();
// 第二个参数,是该用户数据库里的密码 // 第三个参数,是当前类名 } }
14. Shiro配置文件(ShiroConfig)
package cn.xej.config; import at.pollux.thymeleaf.shiro.dialect.ShiroDialect; 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.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.LinkedHashMap; import java.util.Map; @Configuration // 该注解表示该类是 配置类 public class ShiroConfig { // 我的所有方法名都是类名的首字母小写,不然要写成 @Bean(name="xxx") xxx是自定义的方法名 // 配置自定义Realm @Bean public UserRealm userRealm(){ return new UserRealm(); } // 配置安全管理器,把自定义的Realm添加到安全管理器中 @Bean public DefaultWebSecurityManager defaultWebSecurityManager(UserRealm userRealm){ DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager(); defaultWebSecurityManager.setRealm(userRealm); return defaultWebSecurityManager; } // 配置Filter工厂,设置对应的过滤条件和跳转条件,把安全管理器添加到Filter工厂中 @Bean public ShiroFilterFactoryBean shiroFilterFactoryBean(DefaultWebSecurityManager defaultWebSecurityManager){ ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean(); shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager); shiroFilterFactoryBean.setLoginUrl("/welcome"); //设置进入登录页面的url shiroFilterFactoryBean.setUnauthorizedUrl("/unauthorized");
shiroFilterFactoryBean.setSuccessUrl("/index"); //设置登录成功url
Map<String,String> map = new LinkedHashMap<String, String>();
// index和 user/toLogin两个路径不用拦截 map.put("/index","anon"); map.put("/user/toLogin","anon"); map.put("/**","authc"); shiroFilterFactoryBean.setFilterChainDefinitionMap(map); return shiroFilterFactoryBean; } @Bean public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator(); // 强制使用cglib,防止重复代理和可能引起代理出错的问题 defaultAdvisorAutoProxyCreator.setProxyTargetClass(true); return defaultAdvisorAutoProxyCreator; } @Bean public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor(); advisor.setSecurityManager(securityManager); return advisor; } @Bean public ShiroDialect shiroDialect(){ return new ShiroDialect(); } }
15. login.html页面
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script> </head> <body> <form> 账号 <input type="text" name="userId" id="userId"><br/> 密码 <input type="password" name="password" id="password"><br/> <button type="button" onclick="login()">登录</button> </form> <script> function login() { var userId = $(‘#userId‘).val(); var password = $(‘#password‘).val(); $.ajax({ url: ‘/user/toLogin‘, type: ‘post‘, data: { userId: userId, password: password }, success: function (data) { if(data.code===200){ alert(‘登录成功‘); location.href="/index"; // 这里还是走控制器,让控制器来返回具体页面 } } }) } </script> </body> </html>
16. index.html (首页) (当用户没登录时,三元表达式显示游客名字,登录时显示该用户名字,并通过shiro标签执行UserRealm中的授权方法,然后通过该用户的id获取他的角色)
<!DOCTYPE html> <html lang="en" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script> </head> <body> <p>首页</p><br> <a href="javascript:;" th:text="${session.currentUserName}!=null ? ${session.currentUserName} : ‘游客‘"></a><br> <a href="welcome">登录</a> <a href="javascript:;" class="logout">注销</a><br> <div shiro:hasRole="admin"> <a href="user/add">添加教师</a><br> <a href="user/update">更新教师</a><br> </div> <div shiro:hasAnyRoles="admin,teacher"> <a href="user/add">添加学生</a><br> <a href="user/update">更新学生</a><br> </div> <div shiro:hasRole="student"> <a href="user/update">普通学生</a><br> </div> <a href="">游客</a><br> <script> $(‘.logout‘).click(function () { $.ajax({ url: ‘/user/logout‘, type: ‘post‘, success: function (data) { if(data.code===200){ alert("注销成功"); location.href="/index"; } } }) }) </script> </body> </html>
17. 实现用户密码加密
1. 首先在shiro 配置文件中 添加解密规则,如下面的解密凭证器,然后放到UserRealm中
// shiro 解密凭证器 @Bean public HashedCredentialsMatcher hashedCredentialsMatcher(){ HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher(); hashedCredentialsMatcher.setHashAlgorithmName("MD5"); hashedCredentialsMatcher.setHashIterations(1024); return hashedCredentialsMatcher; } // 配置自定义Realm @Bean public UserRealm userRealm(HashedCredentialsMatcher hashedCredentialsMatcher){ UserRealm userRealm = new UserRealm(); userRealm.setCredentialsMatcher(hashedCredentialsMatcher); return userRealm; }
2.然后在UserRealm 认证方法中,返回4个参数
// 认证 @Override protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException { System.out.println("认证"); UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken; User user = userService.findByUserId(token.getUsername()); if(user==null){ return null; } return new SimpleAuthenticationInfo(user,user.getPassword(), ByteSource.Util.bytes(user.getUserId()),getName()); // 第一个参数,将会传到授权中进行获取,比如 User user = (User) principalCollection.getPrimaryPrincipal(); // 第二个参数,是该用户数据库里的密码
// 第三个参数,是salt,这里我是用用户id当作盐
// 第四个参数,是当前类名
}
3. 最后写个测试方法,产生密码加密
“123” 是用户密码
“teacher1” 是加密的盐
1024 是加密次数
String password1 = new SimpleHash("MD5","123","teacher1",1024).toString(); System.out.println("password1 "+password1);
以上是关于springboot 整合 shiro (使用了 thymeleaf模板引擎)的主要内容,如果未能解决你的问题,请参考以下文章