上一篇文章中介绍了Shiro 查看

将Shiro集成到spring boot的步骤:

  (1)定义一个ShiroConfig,配置SecurityManager Bean,SecurityManager为Shiro的安全管理器,管理着所有Subject

  (2)在ShiroConfig中配置ShiroFilterFactoryBean,其为Shiro过滤器工厂类,依赖于SecurityManager

  (3)自定义Realm实现,Realm包含doGetAuthorizationInfo()doGetAuthenticationInfo()方法

实现下用户认证:

  没登录跳转到登录页面;登录后跳转到首页面;注销后,跳转到登录页面

1.引入依赖

  引入Shiro和thymeleaf依赖

<!-- 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>

2.ShiroConfig

  定义一个Shiro配置类,ShiroConfig

package com.sfn.bms.common.shiro;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.*; /**
* Shiro 配置类
*/
@Configuration
public class ShiroConfig { @Bean
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;
} @Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(myShiroRealm());
return securityManager;
} @Bean
public MyShiroRealm myShiroRealm() {
MyShiroRealm shiroRealm = new MyShiroRealm();
return shiroRealm;
} }

  anonauthc等为Shiro为实现的过滤器

3.Realm

  Realm进行实现,然后注入到SecurityManager中

  自定义Realm实现只需继承AuthorizingRealm类,然后实现doGetAuthorizationInfo()和doGetAuthenticationInfo()方法即可

  用户认证只实现doGetAuthenticationInfo()

package com.sfn.bms.common.shiro;
import com.sfn.bms.system.model.User;
import com.sfn.bms.system.service.UserService;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component; /**
* 自定义实现 ShiroRealm,包含认证和授权两大模块
*/
@Component("shiroRealm")
public class MyShiroRealm extends AuthorizingRealm { @Autowired
private UserService userService; /**
* 授权模块,获取用户角色和权限
*
* @param principal principal
* @return AuthorizationInfo 权限信息
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) { return null;
} /**
* 用户认证
*
* @param token AuthenticationToken 身份认证 token
* @return AuthenticationInfo 身份认证信息
* @throws AuthenticationException 认证相关异常
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException { // 获取用户输入的用户名和密码
String account = (String) token.getPrincipal();
String password = new String((char[]) token.getCredentials()); // 通过用户名到数据库查询用户信息
User user = userService.findByAccount(account); 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;
} }

4.数据层

  数据表User

  实体类User

package com.sfn.bms.system.model;

import javax.persistence.*;

public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Short id; /**
* 账号
*/
private String account; /**
* 密码
*/
private String password; /**
* 邮箱
*/
private String email; /**
* 状态 1-正常,0-禁用,-1-删除
*/
private Boolean status; /**
* 添加时间
*/
@Column(name = "create_time")
private Integer createTime; /**
* 上次登陆时间
*/
@Column(name = "last_login_time")
private Integer lastLoginTime; /**
* 上次登录IP
*/
@Column(name = "last_login_ip")
private String lastLoginIp; /**
* 登陆次数
*/
@Column(name = "login_count")
private Integer loginCount; /**
* @return id
*/
public Short getId() {
return id;
} /**
* @param id
*/
public void setId(Short id) {
this.id = id;
} /**
* 获取账号
*
* @return account - 账号
*/
public String getAccount() {
return account;
} /**
* 设置账号
*
* @param account 账号
*/
public void setAccount(String account) {
this.account = account == null ? null : account.trim();
} /**
* 获取密码
*
* @return password - 密码
*/
public String getPassword() {
return password;
} /**
* 设置密码
*
* @param password 密码
*/
public void setPassword(String password) {
this.password = password == null ? null : password.trim();
} /**
* 获取邮箱
*
* @return email - 邮箱
*/
public String getEmail() {
return email;
} /**
* 设置邮箱
*
* @param email 邮箱
*/
public void setEmail(String email) {
this.email = email == null ? null : email.trim();
} /**
* 获取状态 1-正常,0-禁用,-1-删除
*
* @return status - 状态 1-正常,0-禁用,-1-删除
*/
public Boolean getStatus() {
return status;
} /**
* 设置状态 1-正常,0-禁用,-1-删除
*
* @param status 状态 1-正常,0-禁用,-1-删除
*/
public void setStatus(Boolean status) {
this.status = status;
} /**
* 获取添加时间
*
* @return create_time - 添加时间
*/
public Integer getCreateTime() {
return createTime;
} /**
* 设置添加时间
*
* @param createTime 添加时间
*/
public void setCreateTime(Integer createTime) {
this.createTime = createTime;
} /**
* 获取上次登陆时间
*
* @return last_login_time - 上次登陆时间
*/
public Integer getLastLoginTime() {
return lastLoginTime;
} /**
* 设置上次登陆时间
*
* @param lastLoginTime 上次登陆时间
*/
public void setLastLoginTime(Integer lastLoginTime) {
this.lastLoginTime = lastLoginTime;
} /**
* 获取上次登录IP
*
* @return last_login_ip - 上次登录IP
*/
public String getLastLoginIp() {
return lastLoginIp;
} /**
* 设置上次登录IP
*
* @param lastLoginIp 上次登录IP
*/
public void setLastLoginIp(String lastLoginIp) {
this.lastLoginIp = lastLoginIp == null ? null : lastLoginIp.trim();
} /**
* 获取登陆次数
*
* @return login_count - 登陆次数
*/
public Integer getLoginCount() {
return loginCount;
} /**
* 设置登陆次数
*
* @param loginCount 登陆次数
*/
public void setLoginCount(Integer loginCount) {
this.loginCount = loginCount;
}
}

  UserService

package com.sfn.bms.system.service;

import com.sfn.bms.common.service.IService;
import com.sfn.bms.system.model.User; public interface UserService extends IService<User> {
User findByAccount(String account);
}

  UserServiceImpl

package com.sfn.bms.system.service.impl;

import com.sfn.bms.common.service.impl.BaseService;
import com.sfn.bms.system.model.User;
import com.sfn.bms.system.service.UserService;
import org.springframework.stereotype.Repository;
import tk.mybatis.mapper.entity.Example; import java.util.List; @Repository("userService")
public class UserServiceImpl extends BaseService<User> implements UserService {
@Override
public User findByAccount(String account) {
Example example = new Example(User.class);
example.createCriteria().andCondition("lower(account)=", account.toLowerCase());
List<User> list = this.selectByExample(example);
return list.isEmpty() ? null : list.get(0);
} }

  UserMapper.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="com.sfn.bms.system.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.sfn.bms.system.model.User">
<!--
WARNING - @mbg.generated
-->
<id column="id" jdbcType="SMALLINT" property="id" />
<result column="account" jdbcType="VARCHAR" property="account" />
<result column="password" jdbcType="CHAR" property="password" />
<result column="email" jdbcType="VARCHAR" property="email" />
<result column="status" jdbcType="BIT" property="status" />
<result column="create_time" jdbcType="INTEGER" property="createTime" />
<result column="last_login_time" jdbcType="INTEGER" property="lastLoginTime" />
<result column="last_login_ip" jdbcType="VARCHAR" property="lastLoginIp" />
<result column="login_count" jdbcType="INTEGER" property="loginCount" />
</resultMap> </mapper>

5.前端页面

  login.html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" th:href="@{/css/login.css}" type="text/css">
<link rel="stylesheet" th:href="@{css/iCheck/minimal/blue.css}" type="text/css">
<link rel="stylesheet" th:href="@{css/app.css}" type="text/css">
<script th:src="@{/js/jquery.min.js}"></script>
</head>
<body>
<div class="login-page">
<!-- Form-->
<div class="form">
<div class="form-toggle"></div>
<div class="form-panel one">
<div class="form-header">
<h1>账户登录</h1>
</div>
<div class="form-content">
<div class="form-group">
<label>用户名</label>
<input type="text" name="account" />
</div>
<div class="form-group">
<label>密码</label>
<input type="password" name="password" />
</div>
<div class="form-group">
<button onclick="login()" id="loginButton">登录</button>
</div>
</div>
</div>
</div> </div>
</body>
<script th:inline="javascript">
var ctx = [[@{/}]];
function login() {
var account= $("input[name='account']").val();
var password = $("input[name='password']").val();
$.ajax({
type: "post",
url: ctx + "login",
data: {"account": account,"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 lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<p>你好![[${user.account}]]</p>
<a th:href="@{/logout}">注销</a>
</body>
</html>

6.Controller方法

LoginController
package com.sfn.bms.system.controller;

import com.sfn.bms.common.domian.ResponseBo;
import com.sfn.bms.common.util.MD5Utils;
import com.sfn.bms.system.model.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.session.Session;
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; @Controller
public class LoginController { @GetMapping("/login")
public String login() {
return "login";
} @PostMapping("/login")
@ResponseBody
public ResponseBo login(String account, String password) {
// 密码MD5加密
password = MD5Utils.encrypt(account, password);
System.out.println(password);
UsernamePasswordToken token = new UsernamePasswordToken(account, password);
// 获取Subject对象
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) {
// 登录成后,即可通过Subject获取登录的用户信息
User user = (User) SecurityUtils.getSubject().getPrincipal(); model.addAttribute("user", user);
return "index";
}
}

7.测试

  启动项目

  浏览器打开

  http://localhost:8080/

  http://localhost:8080/index

  回跳转到http://localhost:8080/login

  输入用户名super密码123456

点注销,根据ShiroConfig的配置filterChainDefinitionMap.put("/logout", "logout"),Shiro会自动帮我们注销用户信息,并重定向到/路径

相关代码 地址

Spring boot后台搭建二集成Shiro实现用户验证的更多相关文章

  1. Spring boot后台搭建二集成Shiro权限控制

    上一篇文章,实现了用户验证 查看,接下来实现下权限控制 权限控制,是管理资源访问的过程,用于对用户进行的操作授权,证明该用户是否允许进行当前操作,如访问某个链接,某个资源文件等 Apache Shir ...

  2. Spring boot后台搭建二集成Shiro添加Remember Me

    上一片文章实现了用户验证  查看 当用户成功登录后,关闭浏览器,重新打开浏览器访问http://localhost:8080,页面会跳转到登录页,因为浏览器的关闭后之前的登录已失效 Shiro提供了R ...

  3. Spring boot后台搭建一使用MyBatis集成Mapper和PageHelper

    目标: 使用 Spring  boot+MyBatis+mysql 集成 Mapper 和 PageHelper,实现基本的增删改查 先建一个基本的 Spring Boot 项目开启 Spring B ...

  4. 七、spring boot 1.5.4 集成shiro+cas,实现单点登录和权限控制

    1.安装cas-server-3.5.2 官网:https://github.com/apereo/cas/releases/tag/v3.5.2 下载地址:cas-server-3.5.2-rele ...

  5. Spring boot后台搭建二为Shiro权限控制添加缓存

    在添加权限控制后,添加方法 查看 当用户访问”获取用户信息”.”新增用户”和”删除用户”的时,后台输出打印如下信息 , Druid数据源SQL监控 为了避免频繁访问数据库获取权限信息,在Shiro中加 ...

  6. Spring Boot 2.X(二):集成 MyBatis 数据层开发

    MyBatis 简介 概述 MyBatis 是一款优秀的持久层框架,支持定制化 SQL.存储过程以及高级映射.它采用面向对象编程的方式对数据库进行 CRUD 的操作,使程序中对关系数据库的操作更方便简 ...

  7. Spring Boot+CXF搭建WebService(转)

    概述 最近项目用到在Spring boot下搭建WebService服务,对Java语言下的WebService了解甚少,而今抽个时间查阅资料整理下Spring Boot结合CXF打架WebServi ...

  8. spring boot+mybatis搭建项目

    一.创建spring boot项目 1.File->New->Project 2.选择 Spring Initializr ,然后选择默认的 url 点击[Next]: 3.修改项目信息 ...

  9. spring boot / cloud (十二) 异常统一处理进阶

    spring boot / cloud (十二) 异常统一处理进阶 前言 在spring boot / cloud (二) 规范响应格式以及统一异常处理这篇博客中已经提到了使用@ExceptionHa ...

随机推荐

  1. c++控制输出的字体颜色

    SetConsoleTextAttribute(参数表).SetConsoleTextAttribute()函数是一个API设置字体颜色和背景色的函数.参数表中使用两个属性(属性之间用,隔开).Get ...

  2. 《基于WEB的独立学院补考重修管理系统研究》论文笔记(二十)

    <基于WEB的独立学院补考重修管理系统研究>论文笔记(1) 一.基本信息 标题:基于WEB的独立学院补考重修管理系统研究 时间:2016 来源:南通大学杏林学院 关键词:WEB:补考重修管 ...

  3. YII2 更新数据不成功

    起因: CLI模式,定时任务.同步其他系统中的DB数据,通过视图的方式. 历程: 原脚本已经写好,实在已经有的基础上修改,增加新的字段. 加了字段后,执行,但始终不成功,表里记录的utime也是能更新 ...

  4. 线程池的使用(ThreadPoolExecutor详解)

    为什么要使用线程池? 线程是一个操作系统概念.操作系统负责这个线程的创建.挂起.运行.阻塞和终结操作.而操作系统创建线程.切换线程状态.终结线程都要进行CPU调度——这是一个耗费时间和系统资源的事情. ...

  5. django-配置静态页面-celery/redis/nginx

    celery生成静态页面 celery_tasks/tasks.py # 生成静态首页 from django.template import loader, RequestContext # tem ...

  6. 使用docker 实现MySQL主从同步/读写分离

    1. 利用 docker 实现 mysql 主从同步 / 读写分离 为了保证数据的完整和安全,mysql 设计了主从同步,一个挂掉还可以用另个.最近重构论坛,想来改成主从吧.担心失误,就先拿 dock ...

  7. Tensorflow细节-P309-高维向量可视化

    import matplotlib.pyplot as plt import tensorflow as tf import numpy as np import os from tensorflow ...

  8. Eclipse 打包运行maven项目

    https://www.cnblogs.com/tangshengwei/p/6341462.html      

  9. Theano安装笔记

    由于实验需要,近三个月来,安装过十几次Theano,基本上每次都是从最基本的nvidia driver装起.总结一些粗浅的安装心得. GPU:Nvidia K40, M40, M60 软件环境:Unb ...

  10. 网络开发Socket和ServerSocket

    已经发表个人公众号 Socket和ServerSocket Socket为"孔"或"插座",创建Socket,打开连接Socket的输入或输出流,对Socket ...