Spring Boot Shiro用户认证

This commit is contained in:
mrbird 2018-05-02 16:02:12 +08:00
parent 1ef29088a0
commit 54f67b0684
16 changed files with 666 additions and 0 deletions

View File

@ -0,0 +1,80 @@
<?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>
<groupId>com.springboot</groupId>
<artifactId>Spring-Boot-Shiro-Authentication</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.7</java.version>
<thymeleaf.version>3.0.2.RELEASE</thymeleaf.version>
<thymeleaf-layout-dialect.version>2.0.1</thymeleaf-layout-dialect.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>1.3.1</version>
</dependency>
<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>
<!-- oracle驱动 -->
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>6.0</version>
</dependency>
<!-- druid数据源驱动 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.6</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,13 @@
package com.springboot;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
}
}

View File

@ -0,0 +1,59 @@
package com.springboot.config;
import java.util.LinkedHashMap;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.springboot.shiro.ShiroRealm;
@Configuration
public class ShiroConfig {
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
shiroFilterFactoryBean.setLoginUrl("/login");
shiroFilterFactoryBean.setSuccessUrl("/index");
shiroFilterFactoryBean.setUnauthorizedUrl("/403");
LinkedHashMap<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
filterChainDefinitionMap.put("/css/**", "anon");
filterChainDefinitionMap.put("/js/**", "anon");
filterChainDefinitionMap.put("/fonts/**", "anon");
filterChainDefinitionMap.put("/img/**", "anon");
filterChainDefinitionMap.put("/druid/**", "anon");
filterChainDefinitionMap.put("/logout", "logout");
filterChainDefinitionMap.put("/", "anon");
filterChainDefinitionMap.put("/**", "authc");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
return securityManager;
}
@Bean(name = "lifecycleBeanPostProcessor")
public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
return new LifecycleBeanPostProcessor();
}
@Bean
public ShiroRealm shiroRealm(){
ShiroRealm shiroRealm = new ShiroRealm();
return shiroRealm;
}
}

View File

@ -0,0 +1,60 @@
package com.springboot.controller;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
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.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import com.springboot.pojo.ResponseBo;
import com.springboot.pojo.User;
import com.springboot.util.MD5Utils;
@Controller
public class LoginController {
@GetMapping("/login")
public String login() {
return "login";
}
@PostMapping("/login")
@ResponseBody
public ResponseBo login(String username, String password) {
password = MD5Utils.encrypt(username, password);
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
Subject subject = SecurityUtils.getSubject();
try {
subject.login(token);
return ResponseBo.ok();
} catch (UnknownAccountException e) {
return ResponseBo.error(e.getMessage());
} catch (IncorrectCredentialsException e) {
return ResponseBo.error(e.getMessage());
} catch (LockedAccountException e) {
return ResponseBo.error(e.getMessage());
} catch (AuthenticationException e) {
return ResponseBo.error("认证失败!");
}
}
@RequestMapping("/")
public String redirectIndex() {
return "redirect:/index";
}
@RequestMapping("/index")
public String index(Model model) {
User user = (User) SecurityUtils.getSubject().getPrincipal();
model.addAttribute("user", user);
return "index";
}
}

View File

@ -0,0 +1,10 @@
package com.springboot.dao;
import org.apache.ibatis.annotations.Mapper;
import com.springboot.pojo.User;
@Mapper
public interface UserMapper {
User findByUserName(String userName);
}

View File

@ -0,0 +1,50 @@
package com.springboot.pojo;
import java.util.HashMap;
import java.util.Map;
public class ResponseBo extends HashMap<String, Object>{
private static final long serialVersionUID = 1L;
public ResponseBo() {
put("code", 0);
put("msg", "操作成功");
}
public static ResponseBo error() {
return error(1, "操作失败");
}
public static ResponseBo error(String msg) {
return error(500, msg);
}
public static ResponseBo error(int code, String msg) {
ResponseBo ResponseBo = new ResponseBo();
ResponseBo.put("code", code);
ResponseBo.put("msg", msg);
return ResponseBo;
}
public static ResponseBo ok(String msg) {
ResponseBo ResponseBo = new ResponseBo();
ResponseBo.put("msg", msg);
return ResponseBo;
}
public static ResponseBo ok(Map<String, Object> map) {
ResponseBo ResponseBo = new ResponseBo();
ResponseBo.putAll(map);
return ResponseBo;
}
public static ResponseBo ok() {
return new ResponseBo();
}
@Override
public ResponseBo put(String key, Object value) {
super.put(key, value);
return this;
}
}

View File

@ -0,0 +1,46 @@
package com.springboot.pojo;
import java.io.Serializable;
import java.util.Date;
public class User implements Serializable{
private static final long serialVersionUID = -5440372534300871944L;
private Integer id;
private String userName;
private String password;
private Date createTime;
private String status;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}

View File

@ -0,0 +1,55 @@
package com.springboot.shiro;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import com.springboot.dao.UserMapper;
import com.springboot.pojo.User;
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private UserMapper userMapper;
/**
* 获取用户角色和权限
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
return null;
}
/**
* 登录认证
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
String userName = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials());
System.out.println("用户" + userName + "认证-----ShiroRealm.doGetAuthenticationInfo");
User user = userMapper.findByUserName(userName);
if (user == null) {
throw new UnknownAccountException("用户名或密码错误!");
}
if (!password.equals(user.getPassword())) {
throw new IncorrectCredentialsException("用户名或密码错误!");
}
if (user.getStatus().equals("0")) {
throw new LockedAccountException("账号已被锁定,请联系管理员!");
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, password, getName());
return info;
}
}

View File

@ -0,0 +1,28 @@
package com.springboot.util;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.util.ByteSource;
public class MD5Utils {
private static final String SALT = "mrbird";
private static final String ALGORITH_NAME = "md5";
private static final int HASH_ITERATIONS = 2;
public static String encrypt(String pswd) {
String newPassword = new SimpleHash(ALGORITH_NAME, pswd, ByteSource.Util.bytes(SALT), HASH_ITERATIONS).toHex();
return newPassword;
}
public static String encrypt(String username, String pswd) {
String newPassword = new SimpleHash(ALGORITH_NAME, pswd, ByteSource.Util.bytes(username + SALT),
HASH_ITERATIONS).toHex();
return newPassword;
}
public static void main(String[] args) {
System.out.println(MD5Utils.encrypt("test", "123456"));
}
}

View File

@ -0,0 +1,75 @@
server:
context-path: /web
spring:
datasource:
druid:
# 数据库访问配置, 使用druid数据源
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: oracle.jdbc.driver.OracleDriver
url: jdbc:oracle:thin:@localhost:1521:ORCL
username: test
password: 123456
# 连接池配置
initial-size: 5
min-idle: 5
max-active: 20
# 连接等待超时时间
max-wait: 30000
# 配置检测可以关闭的空闲连接间隔时间
time-between-eviction-runs-millis: 60000
# 配置连接在池中的最小生存时间
min-evictable-idle-time-millis: 300000
validation-query: select '1' from dual
test-while-idle: true
test-on-borrow: false
test-on-return: false
# 打开PSCache并且指定每个连接上PSCache的大小
pool-prepared-statements: true
max-open-prepared-statements: 20
max-pool-prepared-statement-per-connection-size: 20
# 配置监控统计拦截的filters, 去掉后监控界面sql无法统计, 'wall'用于防火墙
filters: stat
# Spring监控AOP切入点如x.y.z.service.*,配置多个英文逗号分隔
aop-patterns: com.springboot.servie.*
# WebStatFilter配置
web-stat-filter:
enabled: true
# 添加过滤规则
url-pattern: /*
# 忽略过滤的格式
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
# StatViewServlet配置
stat-view-servlet:
enabled: true
# 访问路径为/druid时跳转到StatViewServlet
url-pattern: /druid/*
# 是否能够重置数据
reset-enable: false
# 需要账号密码才能访问控制台
login-username: druid
login-password: druid123
# IP白名单
# allow: 127.0.0.1
# IP黑名单共同存在时deny优先于allow
# deny: 192.168.1.218
# 配置StatFilter
filter:
stat:
log-slow-sql: true
thymeleaf:
cache: false
mybatis:
# type-aliases扫描路径
type-aliases-package: com.springboot.pojo
# mapper xml实现扫描路径
mapper-locations: classpath:mapper/*.xml
property:
order: BEFORE

View File

@ -0,0 +1,23 @@
-- ----------------------------
-- Table structure for T_USER
-- ----------------------------
CREATE TABLE T_USER (
ID NUMBER NOT NULL ,
USERNAME VARCHAR2(20 BYTE) NOT NULL ,
PASSWD VARCHAR2(128 BYTE) NOT NULL ,
CREATE_TIME DATE NULL ,
STATUS CHAR(1 BYTE) NOT NULL
);
COMMENT ON COLUMN T_USER.USERNAME IS '用户名';
COMMENT ON COLUMN T_USER.PASSWD IS '密码';
COMMENT ON COLUMN T_USER.CREATE_TIME IS '创建时间';
COMMENT ON COLUMN T_USER.STATUS IS '是否有效 1有效 0锁定';
-- ----------------------------
-- Records of T_USER
-- ----------------------------
INSERT INTO T_USER VALUES ('2', 'test', '7a38c13ec5e9310aed731de58bbc4214', TO_DATE('2017-11-19 17:20:21', 'YYYY-MM-DD HH24:MI:SS'), '0');
INSERT INTO T_USER VALUES ('1', 'mrbird', '42ee25d1e43e9f57119a00d0a39e5250', TO_DATE('2017-11-19 10:52:48', 'YYYY-MM-DD HH24:MI:SS'), '1');
-- ----------------------------
-- Primary Key structure for table T_USER
-- ----------------------------
ALTER TABLE T_USER ADD PRIMARY KEY (ID);

View File

@ -0,0 +1,17 @@
<?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="com.springboot.dao.UserMapper">
<resultMap type="com.springboot.pojo.User" id="User">
<id column="id" property="id" javaType="java.lang.Integer" jdbcType="NUMERIC"/>
<id column="username" property="userName" javaType="java.lang.String" jdbcType="VARCHAR"/>
<id column="passwd" property="password" javaType="java.lang.String" jdbcType="VARCHAR"/>
<id column="create_time" property="createTime" javaType="java.util.Date" jdbcType="DATE"/>
<id column="status" property="status" javaType="java.lang.String" jdbcType="VARCHAR"/>
</resultMap>
<select id="findByUserName" resultMap="User">
select * from t_user where username = #{userName}
</select>
</mapper>

View File

@ -0,0 +1,97 @@
.login-page {
width: 360px;
padding: 8% 0 0;
margin: auto;
}
.form {
position: relative;
z-index: 1;
background: #ffffff;
max-width: 360px;
margin: 0 auto 100px;
padding: 45px;
text-align: center;
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.2), 0 5px 5px 0 rgba(0, 0, 0, 0.24);
}
.form input {
outline: 0;
background: #f2f2f2;
width: 100%;
border: 0;
margin: 0 0 15px;
padding: 15px;
box-sizing: border-box;
font-size: 14px;
}
.form button {
text-transform: uppercase;
outline: 0;
background: #4caf50;
width: 100%;
border: 0;
padding: 15px;
color: #ffffff;
font-size: 14px;
-webkit-transition: all 0.3 ease;
transition: all 0.3 ease;
cursor: pointer;
}
.form button:hover,
.form button:active,
.form button:focus {
background: #43a047;
}
.form .message {
margin: 15px 0 0;
color: #b3b3b3;
font-size: 12px;
}
.form .message a {
color: #4caf50;
text-decoration: none;
}
.form .register-form {
display: none;
}
.container {
position: relative;
z-index: 1;
max-width: 300px;
margin: 0 auto;
}
.container:before,
.container:after {
content: "";
display: block;
clear: both;
}
.container .info {
margin: 50px auto;
text-align: center;
}
.container .info h1 {
margin: 0 0 15px;
padding: 0;
font-size: 36px;
font-weight: 300;
color: #1a1a1a;
}
.container .info span {
color: #4d4d4d;
font-size: 12px;
}
.container .info span a {
color: #000000;
text-decoration: none;
}
.container .info span .fa {
color: #ef3b3a;
}
body {
background: #76b852; /* fallback for old browsers */
background: -webkit-linear-gradient(right, #76b852, #8dc26f);
background: -moz-linear-gradient(right, #76b852, #8dc26f);
background: -o-linear-gradient(right, #76b852, #8dc26f);
background: linear-gradient(to left, #76b852, #8dc26f);
font-family: Lato,"PingFang SC","Microsoft YaHei",sans-serif;
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,11 @@
<!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>

View File

@ -0,0 +1,38 @@
<!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-1.11.1.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>