SpringBoot整合shiro整合thymeleaf详细示例

SpringBoot整合shiro+thymeleaf详细示例

      • 准备工作:
        • 1.所需的maven依赖
        • 2.搭建测试项目
          • 2.1创建后台结构和所需类
          • 2.2 创建数据库
          • 2.3 编写配置文件
          • 2.4 编写pojo,dao,service包下的类方法
          • 2.5 controller层以及前端页面
        • 3.编写核心配置类
      • 开始测试:

这是一篇头到尾的详细整合演示,让你直接复制就能运行的代码。

准备工作:

1.所需的maven依赖

     
        
            org.apache.shiro
            shiro-spring-boot-web-starter
            1.5.1
        
         
        
            com.github.theborakompanioni
            thymeleaf-extras-shiro
            2.0.0
        
        
        
            org.mybatis.spring.boot
            mybatis-spring-boot-starter
            2.1.1
        

	 
        
            com.alibaba
            druid
            1.1.21
        
        
            log4j
            log4j
            1.2.17
        

        
        
            org.springframework.boot
            spring-boot-starter-jdbc
        
        
            org.springframework.boot
            spring-boot-starter-thymeleaf
        
        
            org.springframework.boot
            spring-boot-starter-web
        
        
        
        
            mysql
            mysql-connector-java
            runtime
        
        
         
        
            junit
            junit
            4.12
        
        
            org.projectlombok
            lombok
            true
        

springboot整合mybatis以及druid数据源请参考我的上一篇博客

2.搭建测试项目

2.1创建后台结构和所需类

SpringBoot整合shiro整合thymeleaf详细示例_第1张图片

2.2 创建数据库

在这里插入图片描述
插入两条用户数据
SpringBoot整合shiro整合thymeleaf详细示例_第2张图片

2.3 编写配置文件

application.yaml:(默认为application.properties,我将它改成了application.yaml格式,如果使用.yaml格式,请严格控制空格数量,不然必定报错)

server:
  port: 8081

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/shirotest?serverTimezone=UTC&useUnicode=true&characterEncoding=UTF-8&useSSL=false
    password: 123456
    username: root
    ###################以下为druid增加的配置###########################
    type: com.alibaba.druid.pool.DruidDataSource
    # 初始化连接池个数
    initialSize: 5
    # 最小连接池个数——》已经不再使用,配置了也没效果
    minIdle: 2
    # 最大连接池个数
    maxActive: 20
    # 配置获取连接等待超时的时间,单位毫秒,缺省启用公平锁,并发效率会有所下降
    maxWait: 60000
    # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
    timeBetweenEvictionRunsMillis: 60000
    # 配置一个连接在池中最小生存的时间,单位是毫秒
    minEvictableIdleTimeMillis: 300000
      # 用来检测连接是否有效的sql,要求是一个查询语句。
    # 如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会起作用
    validationQuery: SELECT 1 FROM DUAL
      # 建议配置为true,不影响性能,并且保证安全性。
    # 申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
    testWhileIdle: true
    # 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
    testOnBorrow: false
    # 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
    testOnReturn: false
    # 打开PSCache,并且指定每个连接上PSCache的大小
    poolPreparedStatements: true
    maxPoolPreparedStatementPerConnectionSize: 20
      # 通过别名的方式配置扩展插件,多个英文逗号分隔,常用的插件有:
      # 监控统计用的filter:stat
      # 日志用的filter:log4j
    # 防御sql注入的filter:wall
    filters: stat,wall,log4j
    # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
    # 合并多个DruidDataSource的监控数据
    useGlobalDataSourceStat: true

  #关闭thymeleaf模板缓存
  thymeleaf:
    cache: false

#配置mybatis
mybatis:
  type-aliases-package: cn.kgc.zhx.pojo
  mapper-locations: classpath:mapper/*.xml

配置log4j.properties(这里我使用的是shiro官方配置)

log4j.rootLogger=INFO, stdout

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m %n

# General Apache libraries
log4j.logger.org.apache=WARN

# Spring
log4j.logger.org.springframework=WARN

# Default Shiro logging
log4j.logger.org.apache.shiro=INFO

# Disable verbose logging
log4j.logger.org.apache.shiro.util.ThreadContext=WARN
log4j.logger.org.apache.shiro.cache.ehcache.EhCache=WARN
2.4 编写pojo,dao,service包下的类方法

pojo包:

User类:


@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private String username;
    private String password;
    private String role;
    private String perm;
}

dao包:

UserMapper接口:

@Mapper
@Repository("userMapper")
public interface UserMapper {
    User selectUser(String username);
}

Usermapper.xml映射文件(映射文件我放在了resources下自己创建的mapper文件里):
在这里插入图片描述

<mapper namespace="cn.kgc.zhx.dao.UserMapper">
    <select id="selectUser" parameterType="string" resultType="User">
        select * from user where username=#{username}
    select>
mapper>

service包:
UserService接口:

public interface UserService {
    User selectUser(String username);
}

UserServiceImpl实现类:

@Service("userService")
public class UserServiceImpl implements UserService{
    @Autowired
    private UserMapper userMapper;
    @Override
    public User selectUser(String username) {
        return userMapper.selectUser(username);
    }
}
2.5 controller层以及前端页面

两个命名空间:xmlns:th=“http://www.thymeleaf.org”
xmlns:shiro=“http://www.thymeleaf.org/thymeleaf-extras-shiro”

(这里一定要看清楚哪个html在哪个包下)
SpringBoot整合shiro整合thymeleaf详细示例_第3张图片
templates.error包下:
500.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
出错了哦,您可以尝试<a href="/tologin">重新登录</a>
</body>
</html>

templates.user包下
add.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>add页面</h2>
</body>
</html>

public.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>公共页面</h2>
</body>
</html>

testano.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>这是一个测试shiro注解的页面</h2>
</body>
</html>

update.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<h2>update页面</h2>
</body>
</html>

templates包下:
index.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org"
                xmlns:shiro="http://www.thymeleaf.org/thymeleaf-extras-shiro">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<div th:if="session.user==null">
    <a th:href="@{/tologin}">登录</a>
</div>
<a th:href="@{/user/toadd}">add</a><br>
<a th:href="@{/user/toupdate}">update</a><br>
<a th:href="@{/user/topublic}">public</a><br>
<div shiro:hasPermission="perm:vip">
    <label>vip会员才能看到这条信息</label>
</div>
<div shiro:hasRole="role:user">
    <label>普通用户才能看到这条信息</label>
</div>
<a th:href="@{/testAno}">测试注解</a>
</body>
</html>

login.html:

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<form th:action="@{/login}" method="post">
    用户名:<input type="text" name="username"><br>
    密码:<input type="password" name="password"><br>
    <input type="submit" value="提交">
    <label th:text="${msg}"></label>
</form>
</body>
</html>

noauth.html:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
您没有权限访问该页面
</body>
</html>

controller层

TestController类(这里的核心关注点就是登录login()方法,其他的都是普通的跳转页面方法):

@Controller
public class TestController {
    @RequestMapping("/tologin")
    public String toLogin(){
        return "login";
    }
    @RequestMapping("/login")
    public String login(String username, String password, Model model){
        //获取当前用户
        Subject subject= SecurityUtils.getSubject();
        //生成一个令牌,封装用户的登陆数据
        UsernamePasswordToken token=new UsernamePasswordToken(username,password);
        try{
            //subject的内置登录方法,这是一个很复杂的shiro内部方法,不用深究为什么(因为我发现点进去也看不到源码,这应该就是shiro告诉我们会用就行)。
            subject.login(token);
            //登录成功
            return "index";
        }catch (UnknownAccountException e){
            //未查询到用户名
            model.addAttribute("msg","账号不存在");
            return "login";
        }catch (IncorrectCredentialsException e){
            //用户名和密码不匹配
            model.addAttribute("msg","密码不正确");
            return "login"; 
        }catch (LockedAccountException e){
        	//用户被锁定
            model.addAttribute("msg","您的账号被锁定,请解除锁定后登录!");
            return "login";
        }
    }
    @RequestMapping("/user/toadd")
    public String add(){
        return "user/add";
    }
    @RequestMapping("/user/toupdate")
    public String update() {
        return "user/update";
    }
    @RequestMapping("/user/topublic")
    public String publicpage() {
        return "user/public";
    }

    @RequestMapping("/noauth")
    public String noauth() {
        return "noauth";
    }
    @RequestMapping("/testAno")
    @RequiresRoles(value = "role:admin")
    public String testAno() {
        return "user/testano";
    }
}

3.编写核心配置类

在编写核心配置类之前,我们先了解下shiro三大组件,了解了以后,代码就很容易编写和理解了。

shiro三大组件:

Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;

SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;

Realm:域,Shiro从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

UserRealm类:

这个类主要的职责就是,对用户进行认证和授权。
(执行顺序一般是先认证,再授权,不知道为啥重写方法的时候授权在上边,可能是我理解的不够透彻)

public class UserRealm extends AuthorizingRealm {
    @Autowired
    UserService userService;
    @Override
    //授权(给用户分配权限)
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection){
    	// SimpleAuthorizationInfo是AuthorizationInfo的一个实现类,它将作为返回值
        SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
        //拿到当前用户
        Subject subject= SecurityUtils.getSubject();
        //取出从认证方法传过来的user信息(注意 用户!=user,用户是一个抽象,user是一个具体)
        User user=(User) subject.getPrincipal();
        //将用户信息放到session里面,以便在前端页面使用
        Session session=subject.getSession();
        session.setAttribute("user",user);
        //给用户授权
        String role = user.getRole();
        String perm = user.getPerm();
        //给此用户添加一个角色
        info.addRole(role);
        //给此用户添加一个权限
        info.addStringPermission(perm);
        return info;
    }

    @Override
    //认证(验证用户名,密码是否正确)
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
    //获取Controller层生成的令牌
        UsernamePasswordToken userToken =(UsernamePasswordToken) authenticationToken;
        //从令牌中取出用户名,查询此用户名是否存在
        String username = userToken.getUsername();
        User user = userService.selectUser(username);
        if (user==null){
        //如果不存在,返回null,controller层的subject.login(token)就会报UnknownAccountException 异常
            return null;
        }else {
        //principal ,credentials,realmName
            return new SimpleAuthenticationInfo(user,user.getPassword(),"userRealm");
        }
    }
}

ShiroConfig类:

shiro的核心配置类

@Configuration
public class ShiroConfig {
 	//创建userRealm对象
    @Bean
    public UserRealm userRealm(){
        return new UserRealm();
    }
    
    //创建DefaultWebSecurityManager(三大组件之一SecurityManager)
    @Bean
    public DefaultWebSecurityManager webSecurityManagerger(@Qualifier("userRealm") UserRealm userRealm){
        DefaultWebSecurityManager securityManager=new DefaultWebSecurityManager();
        //关联userRealm
        securityManager.setRealm(userRealm);
        return securityManager;
    }
    
    //创建ShiroFilterFactoryBean(这里就相当一shior的过滤器)
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("webSecurityManagerger") DefaultWebSecurityManager webSecurityManager){
        ShiroFilterFactoryBean bean =new ShiroFilterFactoryBean();
        //关联DefaultWebSecurityManager
        bean.setSecurityManager(webSecurityManager);
        //设置认证失败返回的登录页面
        bean.setLoginUrl("/tologin");
        //设置权限不够跳转的提示页面
        bean.setUnauthorizedUrl("/noauth");

        Map<String,String> filterMap=new HashMap<String, String>();
        /**
         *添加shiro内置过滤:
         *anon 没有参数,表示可以匿名使用。
         * authc:没有参数,表示需要认证(登录)才能使用
         * roles:例子roles[admin],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,当有多个参数时,例如roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。
         * perms:例子perms[user:add:*],参数可以写多个,多个时必须加上引号,并且参数之间用逗号分割,例如perms["user:add:*,user:modify:*"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
         * rest:例子rest[user],根据请求的方法,相当于/admins/user/**=perms[user:method] ,其中method为post,get,delete等。
         * port:例子port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString是你访问的url里的?后面的参数。
         * authcBasic:没有参数,表示httpBasic认证
         * ssl:没有参数,表示安全的url请求,协议为https
         * user没有参数,表示必须存在用户,当登入操作时不做检查
         */
        filterMap.put("/user/public","authc");

        filterMap.put("/user/toupdate","perms[perm:vip]");

        filterMap.put("/user/toadd","roles[role:admin]");

        //权限配置的map集合
        bean.setFilterChainDefinitionMap(filterMap);
        return bean;
    }


    //整合thymeleaf
    @Bean
    public  ShiroDialect getShiroDialect(){
        return new ShiroDialect();
    }
}

到此为止,整个项目就准备就绪了。

开始测试:

  1. 运行springboot容器

  2. 进入登录页面:http://localhost:8081/tologin

  3. 登录测试

    3.1.用户名错误测试
    SpringBoot整合shiro整合thymeleaf详细示例_第4张图片
    3.2.密码错误测试
    SpringBoot整合shiro整合thymeleaf详细示例_第5张图片

  4. admin用户访问测试(这里请参考ShiroConfig授权,TestController控制跳转,以及html页面代码)
    SpringBoot整合shiro整合thymeleaf详细示例_第6张图片
    主页面OK
    SpringBoot整合shiro整合thymeleaf详细示例_第7张图片
    add页面OK
    SpringBoot整合shiro整合thymeleaf详细示例_第8张图片
    update页面OK
    SpringBoot整合shiro整合thymeleaf详细示例_第9张图片
    public页面OK
    SpringBoot整合shiro整合thymeleaf详细示例_第10张图片
    通过注解访问控制OK

  5. user用户访问测试
    SpringBoot整合shiro整合thymeleaf详细示例_第11张图片
    主页面OK
    在这里插入图片描述
    add和update页面无权访问
    SpringBoot整合shiro整合thymeleaf详细示例_第12张图片
    public页面OK
    在这里插入图片描述
    通过注解访问控制报500异常,我重写了500页面,进行引导,测试OK

到这里,一整套的整合就结束了!shiro比较于springscruity结构更加复杂,调用方式比较乱,但是并不难,需要多加练习。

你可能感兴趣的:(springboot)