Springboot整合Shiro
创建一个springboot项目
- 引入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.创建一张用户表
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>