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&amp&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模板引擎)的主要内容,如果未能解决你的问题,请参考以下文章

springboot学习笔记-5 springboot整合shiro

springboot整合shiro

SpringBoot整合Shiro

springboot整合shiro应用

SpringBoot 优雅的整合 Shiro

Shiro整合springboot以及自定义Realm