上一篇文章中介绍了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. 《Coderxiaoban团队》团队作业5:项目需求分析改进与系统设计

    实验八 <Coderxiaoban团队>团队作业5:项目需求分析改进与系统设计 项目 内容 这个作业属于哪个课程 任课教师博客主页链接 这个作业的要求在哪里 团队作业5:项目需求分析改进与 ...

  2. PHP程序员最容易犯的Mysql错误

    对于大多数web应用来说,数据库都是一个十分基础性的部分.如果你在使用PHP,那么你很可能也在使用MySQL—LAMP系列中举足轻重的一份子. 对于很多新手们来说,使用PHP可以在短短几个小时之内轻松 ...

  3. SparkSQL读写外部数据源-jext文件和table数据源的读写

    object ParquetFileTest { def main(args: Array[String]): Unit = { val spark = SparkSession .builder() ...

  4. Java 第十次作业

    题目1:计算通过中介买房的过程,需交纳的中介费和契税. 代码 /** Business接口中: 两个成员变量RATIO,TAX分别代表房屋中介收取的中介费用占房屋标价的比例及购房需要交纳的契税费用占房 ...

  5. IN8005 Exercise Session

    Exercise Session for Introductioninto Computer Science(for non Informatics studies, TUM BWL)(IN8005) ...

  6. 使用docker搭建etcd

    下载etcd代码然后拷贝到服务器 来自为知笔记(Wiz)

  7. rust cargo 一些方便的三方cargo 子命令扩展

    内容来自cargo 的github wiki,记录下,方便使用 可选的列表 cargo-audit - Audit Cargo.lock for crates with security vulner ...

  8. Lightning Web Components html_templates(三)

    Lightning Web Components 强大之处在于模版系统,使用了虚拟dom 进行智能高效的组件渲染. 使用简单语法以声明方式将组件的模板绑定到组件的JavaScript类中的数据 数据绑 ...

  9. Linux 系统管理——服务器RAID及配置实战

    RAID称为廉价磁盘冗余阵列.RAID的基本想法是把多个便宜的小磁盘组合在一起.成为一个磁盘组,使性能达到或超过一个容量巨大.价格昂贵的磁盘. 2.级别介绍 RAID 0连续以位或字节为单位分割数据, ...

  10. 括号匹配(POJ2955)题解

    原题地址:http://poj.org/problem?id=2955 题目大意:给出一串括号,求其中的最大匹配数. 我这么一说题目大意估计很多人就蒙了,其实我看到最开始的时候也是很蒙的.这里就来解释 ...