Springboot整合Shiro

创建一个springboot项目

  1. 引入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 http://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.0.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.emep.byp</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>

		<!-- MyBatis -->
		<dependency>
			<groupId>org.mybatis.spring.boot</groupId>
			<artifactId>mybatis-spring-boot-starter</artifactId>
			<version>1.3.1</version>
		</dependency>
		<!-- thymeleaf -->
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<!-- shiro-spring -->
		<dependency>
			<groupId>org.apache.shiro</groupId>
			<artifactId>shiro-spring</artifactId>
			<version>1.4.0</version>
		</dependency>

		<!-- MySQL驱动 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
		</dependency>

		<!-- druid数据源驱动 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid-spring-boot-starter</artifactId>
			<version>1.1.10</version>
		</dependency>

		<!-- 通用mapper -->
		<dependency>
			<groupId>tk.mybatis</groupId>
			<artifactId>mapper-spring-boot-starter</artifactId>
			<version>1.1.5</version>
		</dependency>
		<!-- pagehelper 分页插件 -->
		<dependency>
		<groupId>com.github.pagehelper</groupId>
		<artifactId>pagehelper-spring-boot-starter</artifactId>
		<version>1.2.3</version>
		</dependency>

		<!-- commons -->
		<dependency>
			<groupId>org.apache.commons</groupId>
			<artifactId>commons-lang3</artifactId>
		</dependency>

		<!-- google.guava/guava -->
		<dependency>
			<groupId>com.google.guava</groupId>
			<artifactId>guava</artifactId>
			<version>25.1-jre</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>


           <!-- <plugin>
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.3.5</version>
                <dependencies>
                    <dependency>
                        <groupId>mysql</groupId>
                        <artifactId>mysql-connector-java</artifactId>
                        <version>${mysql.version}</version>
                    </dependency>
                    <dependency>
                        <groupId>tk.mybatis</groupId>
                        <artifactId>mapper</artifactId>
                        <version>3.4.0</version>
                    </dependency>
                </dependencies>
                <executions>
                    <execution>
                        <id>Generate MyBatis Artifacts</id>
                        <phase>package</phase>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <verbose>true</verbose>
                    <overwrite>true</overwrite>
                    <configurationFile>src/main/resources/mybatis-generator.xml</configurationFile>
                </configuration>
            </plugin>-->


		</plugins>
	</build>

</project>

2.创建一张用户表
Springboot整合Shiro

3.配置application.yml

server:
  port: 80
  tomcat:
    uri-encoding: UTF-8
spring:
  datasource:
    druid:
      # \u6570\u636E\u5E93\u8BBF\u95EE\u914D\u7F6E, \u4F7F\u7528druid\u6570\u636E\u6E90
      db-type: com.alibaba.druid.pool.DruidDataSource
      driverClassName: com.mysql.jdbc.Driver
      url: jdbc:mysql://127.0.0.1:3306/mrbird?useUnicode=true&characterEncoding=utf8
      username: root
      password: root
      # \u8FDE\u63A5\u6C60\u914D\u7F6E
      initial-size: 5
      min-idle: 5
      max-active: 20
      # \u8FDE\u63A5\u7B49\u5F85\u8D85\u65F6\u65F6\u95F4
      max-wait: 30000
      # \u914D\u7F6E\u68C0\u6D4B\u53EF\u4EE5\u5173\u95ED\u7684\u7A7A\u95F2\u8FDE\u63A5\u95F4\u9694\u65F6\u95F4
      time-between-eviction-runs-millis: 60000
      # \u914D\u7F6E\u8FDE\u63A5\u5728\u6C60\u4E2D\u7684\u6700\u5C0F\u751F\u5B58\u65F6\u95F4
      min-evictable-idle-time-millis: 300000
      validation-query: select '1' from dual
      test-while-idle: true
      test-on-borrow: false
      test-on-return: false
      # \u6253\u5F00PSCache\uFF0C\u5E76\u4E14\u6307\u5B9A\u6BCF\u4E2A\u8FDE\u63A5\u4E0APSCache\u7684\u5927\u5C0F
      pool-prepared-statements: true
      max-open-prepared-statements: 20
      max-pool-prepared-statement-per-connection-size: 20
      # \u914D\u7F6E\u76D1\u63A7\u7EDF\u8BA1\u62E6\u622A\u7684filters, \u53BB\u6389\u540E\u76D1\u63A7\u754C\u9762sql\u65E0\u6CD5\u7EDF\u8BA1, 'wall'\u7528\u4E8E\u9632\u706B\u5899
      filters: stat
      # Spring\u76D1\u63A7AOP\u5207\u5165\u70B9\uFF0C\u5982 x.y.z.service.*,\u914D\u7F6E\u591A\u4E2A\u82F1\u6587\u9017\u53F7\u5206\u9694
      #aop-patterns: cc.mrbird.*.service.*

      # WebStatFilter\u914D\u7F6E
      web-stat-filter:
        enabled: true
        # \u6DFB\u52A0\u8FC7\u6EE4\u89C4\u5219
        url-pattern: /*
        # \u5FFD\u7565\u8FC7\u6EE4\u7684\u683C\u5F0F
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*,/actuator/*'

      # StatViewServlet\u914D\u7F6E
      stat-view-servlet:
        enabled: true
        # \u8BBF\u95EE\u8DEF\u5F84\u4E3A/druid\u65F6\uFF0C\u8DF3\u8F6C\u5230StatViewServlet
        url-pattern: /druid/*
        # \u662F\u5426\u80FD\u591F\u91CD\u7F6E\u6570\u636E
        reset-enable: false
        # \u9700\u8981\u8D26\u53F7\u5BC6\u7801\u624D\u80FD\u8BBF\u95EE\u63A7\u5236\u53F0
        login-username: druid
        login-password: druid123
        # IP\u767D\u540D\u5355
        # allow: 127.0.0.1
        # IP\u9ED1\u540D\u5355\uFF08\u5171\u540C\u5B58\u5728\u65F6\uFF0Cdeny\u4F18\u5148\u4E8Eallow\uFF09
        # deny: 192.168.1.218

      # \u914D\u7F6EStatFilter
      filter:
        stat:
          log-slow-sql: true

  mvc:
    static-path-pattern: /**
  resources:
    static-locations: classpath:/static/

  thymeleaf:
    #prefix: classpath:/templates/
    #suffix: .html
    cache: false #\u5173\u95ED\u7F13\u5B58

mybatis:
  # type-aliases\u626B\u63CF\u8DEF\u5F84
  type-aliases-package: com.emep.byp.entity
  # mapper xml\u5B9E\u73B0\u626B\u63CF\u8DEF\u5F84
  mapper-locations: classpath:mapper/*.xml

mapper:
  mappers: com.emep.byp.util.MyMapper
  not-empty: false
  identity: MYSQL

pagehelper:
  helperDialect: mysql
  reasonable: true
  supportMethodsArguments: true
  params: count=countSql
  

4.编写ShiroConfig

package com.emep.byp.config;

import com.emep.byp.realm.ShiroRealm;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;

/**
 * Created by Administrator on 2019/3/9/009.
 */
@Configuration
public class ShiroConfig {

    private Logger logger = LoggerFactory.getLogger(ShiroConfig.class);

    /**
     * Filter工厂,设置对应的过滤条件和跳转条件
     * @param securityManager
     * @return
     */
    @Bean(name="shirFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // 设置securityManager
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 登录的url
        shiroFilterFactoryBean.setLoginUrl("/login");
        // 登录成功后跳转的url
        shiroFilterFactoryBean.setSuccessUrl("/index");
        // 未授权url
        shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        LinkedHashMap<String,String> filterChainDefinitionMap = new LinkedHashMap<>();
        // 定义filterChain,静态资源不拦截
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/fonts/**", "anon");
        filterChainDefinitionMap.put("/img/**", "anon");
        // druid数据源监控页面不拦截
        filterChainDefinitionMap.put("/druid/**", "anon");
        // 配置退出过滤器,其中具体的退出代码Shiro已经替我们实现了
        filterChainDefinitionMap.put("/logout", "logout");
        filterChainDefinitionMap.put("/", "anon");
        // 除上以外所有url都必须认证通过才可以访问,未通过认证自动访问LoginUrl
        filterChainDefinitionMap.put("/**", "authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 权限管理,配置主要是Realm的管理认证
     * @return
     */
    @Bean
    public SecurityManager securityManager(){

        logger.info("- - - - - - -shiro开始加载- - - - - - ");
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        // 配置SecurityManager,并注入shiroRealm
        defaultWebSecurityManager.setRealm(shiroRealm());
        // 配置 rememberMeCookie
        //defaultWebSecurityManager.setRememberMeManager(rememberMeManager());
        // 配置 缓存管理类 cacheManager
        //defaultWebSecurityManager.setCacheManager(cacheManager());
        //defaultWebSecurityManager.setSessionManager(sessionManager());
        return defaultWebSecurityManager;
    }


    /**
     * 凭证匹配器
     * (由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     * )
     * @return
     */
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName("md5");//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(2);//散列的次数,比如散列两次,相当于 md5(md5(""));
        return hashedCredentialsMatcher;
    }

    @Bean
    public ShiroRealm shiroRealm() {
        // 配置 Realm,需自己实现,见 com.emep.byp.realm.ShiroRealm
        ShiroRealm shiroRealm = new ShiroRealm();
        shiroRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return new ShiroRealm();
    }

    @Bean(name = "lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor(){
        // Shiro生命周期处理器
        return new LifecycleBeanPostProcessor();
    }

    /**
     * AOP式方法级权限检查
     * @return
     */
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    /**
     *  开启shiro aop注解支持.
     *  使用代理方式;所以需要开启代码支持;
     * @param securityManager
     * @return
     */
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;

    }

}

5.编写自定义realm

package com.emep.byp.realm;

import com.emep.byp.dao.SysUserMapper;
import com.emep.byp.entity.SysUser;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.springframework.beans.factory.annotation.Autowired;

/**
 * Created by Administrator on 2019/3/9/009.
 */
public class ShiroRealm extends AuthorizingRealm {

    @Autowired
    SysUserMapper sysUserMapper;

    /**
     * 用户授权
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {

        return null;
    }

    /**
     * 用户认证
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) token;
        // 获取用户输入的用户名和密码
        String username = (String) usernamePasswordToken.getPrincipal();
        System.out.println(token.getCredentials());
        //通过username从数据库中查找 User对象,如果找到,没找到.
        //实际项目中,这里可以根据实际情况做缓存,如果不做,Shiro自己也是有时间间隔机制,2分钟内不会重复执行该方法
        String passWord = new String((char[]) usernamePasswordToken.getCredentials());

        System.out.println("用户" + username + "认证-----ShiroRealm.doGetAuthenticationInfo");

        // 通过用户名到数据库查询用户信息
        SysUser user = this.sysUserMapper.findByName(username);
        if(user==null)
            throw new UnknownAccountException("用户名或密码错误!");
        if (!passWord.equals(user.getPassword()))
            throw new IncorrectCredentialsException("用户名或密码错误!");
        if (SysUser.STATUS_LOCK.equals(user.getStatus()))
            throw new LockedAccountException("账号已被锁定,请联系管理员!");
        //ByteSource.Util.bytes(user.getCredentialsSalt()),//salt=username+salt
        //byte[] salt = Encodes.decodeHex(user.getSalt());
        //ByteSource.Util.bytes(salt),
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassword(), getName());

        return info;
    }

    /**
     * 清除权限缓冲,使用方法:在需要清除用户权限的地方注入 ShiroRealm,然后调用其clearCache方法。
     */
    public void clearCache(){
        PrincipalCollection principalCollection = SecurityUtils.getSubject().getPrincipals();
        super.clearCache(principalCollection);
    }
}

6.编写登录后台代码

package com.emep.byp.controller;

import com.emep.byp.entity.SysUser;
import com.emep.byp.util.MD5Utils;
import com.emep.byp.util.ResponseBo;
import com.google.common.collect.Maps;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

/**
 * Created by Administrator on 2019/3/9/009.
 */
@Controller
public class LoginController {

    private final Logger logger = LoggerFactory.getLogger(LoginController.class);

    @GetMapping("/login")
    public String login() {
        logger.info("开始登录...");
        return "login";
    }

    @PostMapping("/login")
    @ResponseBody
    public ResponseBo login(String username, String password) {
        // 密码MD5加密
        password = MD5Utils.encrypt(username, password);
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        // 获取Subject对象
        String msg="";
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(token);
            return ResponseBo.ok();
        } catch (UnknownAccountException e) {
            return ResponseBo.error("账号不存在");
        } catch (IncorrectCredentialsException e) {
            return ResponseBo.error("密码不正确");
        } catch (LockedAccountException e) {
            return ResponseBo.error("账号被锁定");
        } catch (AuthenticationException e) {
            return ResponseBo.error("认证失败!");
        }
    }

    @RequestMapping("/")
    public String redirectIndex() {
        logger.info("index页面。。。");
        return "redirect:/index";
    }

    @RequestMapping("/index")
    public String index(Model model) {
        // 登录成后,即可通过Subject获取登录的用户信息
        SysUser user = (SysUser) SecurityUtils.getSubject().getPrincipal();
        model.addAttribute("user", user);
        return "index";
    }

    @RequestMapping("/403")
    public String unauthorizedRole(){
        System.out.println("------没有权限-------");
        return "403";
    }

}

7.编写页面

login.html

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
    <link rel="stylesheet" th:href="@{/css/login.css}" type="text/css">
    <script th:src="@{/js/jquery.min.js}"></script>
</head>
<body>
<div class="login-page">
    <div class="form">
        <input type="text" placeholder="用户名" name="username" required="required"/>
        <input type="password" placeholder="密码" name="password" required="required"/>
        <button onclick="login()">登录</button>
    </div>
</div>
</body>
<script th:inline="javascript">
    var ctx = [[@{/}]];
    function login() {
        var username = $("input[name='username']").val();
        var password = $("input[name='password']").val();
        $.ajax({
            type: "post",
            url: ctx + "login",
            data: {"username": username,"password": password},
            dataType: "json",
            success: function (r) {
                if (r.code == 0) {
                    location.href = ctx + 'index';
                } else {
                    alert(r.msg);
                }
            }
        });
    }
</script>
</html>

index.html页面

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<p>你好![[${user.username}]]</p>
<a th:href="@{/logout}">注销</a>
</body>
</html>