文章目录

第 7 章 登录与认证

学习目标

能够使用 BCrypt 进行密码加密 完成 Spring Security 入门 完成青橙登录认证 完成青橙菜单展示 完成用户登录日志 项目序列 - 7:https://github.com/Jonekaka/https-github.com-Jonekaka-javaweb-qingcheng-7-82.git

1. BCrypt 密码加密

1.1 BCrypt 简介

用户输入账户密码,密码信息被加密后传送,和数据库中的加密数据比对 即使信息被拦截也无法逆运算出原来的密码

随着技术的发展,md5 已经不安全了,很多人会撞库,将所有的可能的密码的 md5 存储起来,如果有相同的就能解密了 期间并没有运算,而是查询

新的思路是,即是字符串一样,但是计算出的加密字符串不同,加入 “随机盐”,就像佐料一样 目前,MD5 和 BCrypt 比较流行。相对来说,BCrypt 比 MD5 更安全。 BCrypt 官网 http://www.mindrot.org/projects/jBCrypt/

1.2 快速入门

(1)我们从官网下载源码 (2)新建工程,将源码类 BCrypt 拷贝到工程 (3)新建测试类,main 方法中编写代码,实现对密码的加密 输出随机盐 将随机盐加入密码字符串 每次得到的结果都不同

$2a$10$X4c5c.fycm31rBAN53QoFO  随机盐
$2a$10$X4c5c.fycm31rBAN53QoFOk01uC6YJ3oxNpH549puuFQ8Qmu08Ha6   加密后密码字符串

$2a$10$6kKtFnRmOG3WrTwjuBOdre
$2a$10$6kKtFnRmOG3WrTwjuBOdreNI6.6MxVb0VSHBKYao6MOO40.u/axzS

String gensalt = BCrypt.gensalt();//这个是盐 29个字符,随机生成
        System.out.println(gensalt);
        String password = BCrypt.hashpw("123456", gensalt); //根据盐对密码进行加密
        System.out.println(password);//加密后的字符串前29位就是盐

(4)新建测试类,main 方法中编写代码,实现对密码的校验。BCrypt 不支持反运算,只支持密码校验。 以前 md5 还可以比对加密后的字符串,现在无法比对,只能使用 BC 自带的校验方法

这样通过穷举就不行了,每次字符串不一样根本无法比对,但是通过内构方法,可以轻易得出结论

boolean checkpw = BCrypt.checkpw("123456",
        "$2a$10$61ogZY7EXsMDWeVGQpDq3OBF1.phaUu7.xrwLyWFTOu8woE08zMIW");
        System.out.println(checkpw);

2. 安全框架 Spring Security

2.1 Spring Security 简介

没有安全框架之前的登录验证 使用 session 保存登录信息,为空则强制跳转登录页面

2.1.1 安全框架概述

什么是安全框架? 解决系统安全问题的框架。管理访问控制权限,通过配置的方式实现对资源的访问限制。

2.1.2 常用安全框架

Spring Security:spring 家族一员。是一个能够为基于 Spring 的企业应用系统提供声明式的安全访问控制解决方案的安全框架。 它提供了一组可以在 Spring 应用上下文中配置的 Bean,充分利用了 Spring IoC,DI(控制反转 Inversion of Control ,DI:Dependency Injection 依赖注入)和 AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

Apache Shiro 是一个功能强大且易于使用的 Java 安全框架, 提供了认证, 授权, 加密, 和会话管理。

2.1.3 认证与授权

认证:限制用户只能登陆才可以访问资源。 授权:限制用户必须有某资源的访问权限才可以访问。 这里主要解释认证,授权会在下一次介绍中详细介绍

2.2 快速入门

2.2.1 最简单案例

需求:实现简单的登陆,当用户没有登陆访问主页执行拦截跳转到登陆,登陆后跳转到 主页。实现退出登陆的功能,退出后再次访问主页仍然拦截。 为了简化登录环节,这里 用户名和密码不连接数据 库,直接在配置文件中配置。 (1)新建 war 工程,pom 文件引入依赖

 <packaging>war</packaging>
    <properties>
        <spring.version>5.0.5.RELEASE</spring.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <configuration>
                    <!‐‐ 指定端口 ‐‐>
                    <port>9090</port>
                    <!‐‐ 请求路径 ‐‐>
                    <path>/</path>
                </configuration>
            </plugin>
        </plugins>
    </build>
 

(2)创建 webapp/WEB-INF/web.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
             xmlns:beans="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security.xsd">
    <!--页面拦截规则 -->
    <http>
        <intercept-url pattern="/**" access="hasRole('ROLE_ADMIN')" />
        <form-login/>
        <logout/>
    </http>
    <!--认证管理器-->
    <authentication-manager>
    <authentication-provider>
    <user-service>
    <user name="admin" password="{noop}123456"
          authorities="ROLE_ADMIN"/>
</user-service>
        </authentication-provider>
        </authentication-manager>
        </beans:beans>
 

(3)resources 下创建 spring-security.xml

pattern=”/” 页面的匹配规则,/* 拦截一级文件夹,二级不会拦截,/ 拦截所有父和子 所有资源决定什么角色可以访问 hasRole(‘ROLE_ADMIN’) 决定什么角色可以访问

<form‐login/> 当前工程自动实现表单登录,看起来很智能 自动为表单配置一个退出登录 <authentication‐provider> 认证提供者,用来配置用户信息的 当用户使用 admin 登录时就会拥有 admin 的角色权限;密码加密策略,如果密码本身未加密,就设置加密策略,no op 无操作不加密的意思

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns="http://java.sun.com/xml/ns/javaee"
        xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
        http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
        version="2.5">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-security.xml</param-value>
        </context-param>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
        </listener>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        </filter>,
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
        </filter-mapping>
        </web-app>
 

{noop} 是制定密码加密策略为不加密 。noop 的意思是明文保存的密码 (noop: No Operation) (4)webapp 下创建 index.html,内容随意。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
登录成功
<body>
 
</body>
</html>
 

(5)启动工程,打开浏览器输入地址 http://localhost:9090 ,浏览器显示 这个登陆页面时 SpringSecurity 帮我们自动生成的。

假如试图直接访问 index.html,则会强制返回登录页面 此处创建隐藏域是为了防止跨站访问入侵,就是黑客复制用户的登录 cookie 信息,不用用户名与密码登录 其在隐藏域中加入了 tocken, 服务端会拉取验证 tocken

输入正确的密码,进入首页,输入错误的密码显示如下信息

如果成功登录,报错如下 这是因为其会自动寻找图标文件

如图所示

直接访问 index.html 即可, 此时就可以访问了,而不是被强制送回登录页面

配置说明: intercept-url 表示拦截页面 /* 表示的是该目录下的资源,只包括本级目录不包括下级目录 /** 表示的是该目录以及该目录下所有级别子目录的资源 form-login 为开启表单登陆

2.2.2 密码加密策略

修改配置文件中的 password 为 bcrypt 加密后的密码,并制定加密策略为 bcrypt

<user‐service>
<user name="admin"
      password="
{bcrypt}$2a$10$61ogZY7EXsMDWeVGQpDq3OBF1.phaUu7.xrwLyWFTOu8woE08zMIW"
      authorities="ROLE_ADMIN"/>
</user‐service>
 
 

spring security 官方推荐使用更加安全的 bcrypt 加密方式。spring security 5 支持的加密 方式有 bcrypt、ldap、MD4、MD5、noop、pbkdf2、scrypt、SHA-1、SHA-256、 sha256。

我们还有另外一种配置方式,来制定加密策略 在登录时,对密码加密

<!‐‐认证管理器‐‐>
<authentication‐manager>
<authentication‐provider>
<user‐service>
<user name="admin"
      password="$2a$10$EPtdfwSJ0ABj5JsCyLqhFe1g503DgA4lQvOxyZF/3usoyje5/q/Dy"
      authorities="ROLE_ADMIN"></user>
</user‐service>
<password‐encoder ref="bcryptEncoder"></password‐encoder>
</authentication‐provider>
</authentication‐manager>
<beans:bean id="bcryptEncoder"
            class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"
/>
 

2.2.3 自定义登录页

(1)创建页面 login.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF‐8">
    <title>登录</title>
</head>
<body>
<form action="/login" method="post">
    <table>
        <tr>
            <td>用户名</td>
            <td><input name="username"> </td>
        </tr>
        <tr>
            <td>密码</td>
            <td><input type="password" name="password"> </td>
        </tr>
    </table>
    <button>登录</button>
</form>
</body>
</html>
 
 

(2)创建 login_error.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF‐8">
    <title>登录错误</title>
</head>
<body>
用户名和密码错误!
</body>
</html>
 

(3)修改 spring-security.xml 拦截规则部分 指定 spring 的登录界面,不使用默认的 default‐target‐url=“/index.html” 指定登陆之后默认进入的界面 authentication‐failure‐url=“/login_error.html” 指定密码错误界面

登录界面与错误界面不需要拦截,否则什么都看不大

<!‐‐ 以下页面不被拦截 ‐‐>
<http pattern="/login.html" security="none"></http>
<http pattern="/login_error.html" security="none"></http>
<!‐‐ 页面拦截规则 ‐‐>
<http>
    <intercept‐url pattern="/**" access="hasRole('ROLE_ADMIN')" />
    <form‐login login‐page="/login.html" default‐target‐url="/index.html" authentication‐failure‐url="/login_error.html"/>
    <csrf disabled="true"/>
</http>
 
 

(4)测试,浏览器显示了我们自定义的登录页面 为关闭跨域请求伪造控制。就是用户登录后会生成 cookie, 如果被黑客拿到,就可以用 cookie 伪造登录 spring 为了避免这种情况,为登录创造 tocken, 放到表单的隐藏域中,服务端会验证 tocken 如果使用了自定义的页面,没有创建 tocken, 不存在,会提示 403 错误, 因为静态页无法动态生成 token,所以将此功能关闭。一般静态页采用图形验证码的方式实现防止跨域请求伪造的功 能。

2.2.4 UserDetailsService

刚才是将用户名和密码配置在配置文件中, 实际的是从数据库中提取用户名和密码信息,如何做到呢? UserDetailsService 的使用。sp_security 提供的 (1)创建 UserDetailsServiceImpl 这里把用户角色,密码写死了,实际上是从数据库中查询的 密码的比对由数据库完成 根据用户名和密码为用户分配角色,返回角色对象

public class UserDetailServiceImpl implements UserDetailsService {
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
        grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        return new User(username,"$2a$10$7NIn8msrQVfJEieatLfrT.Bnh7b4tJ9qmJS0oq8r.KGYPdziPcAjS",grantedAuthorities)
    }
}
 

(2)修改配置文件 spring-security.xml 认证管理器部分 认证管理由 - ref=“userDetailsService” 负责

<!--认证管理器-->
    <authentication-manager>
        <authentication-provider user-service-ref="userDetailsService">
             <password-encoder ref="bcryptEncoder"></password-encoder>
        </authentication-provider>
    </authentication-manager>
    <beans:bean id="userDetailsService" class="com.test.verify.UserDetailServiceImpl"/>
    <beans:bean id="bcryptEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
 
 

此时登录认证

3. 青橙登录认证

3.1 青橙登录页

3.1.1 需求分析

实现管理后台的登录功能。登录页采用前端提供的页面。 登录

后台主界面

3.1.2 代码实现

qingcheng_common_web 已经添加了 springsecurity 依赖 (1)添加登录页面。修改 login.html , 指定表单提交地址为 / login , 用户名 和密码框的 name 为 username 和 password 表单提交时会将数据传送至 login

<form class="loginForm clear" action="/login" method="POST">
 
<input class="el-input__inner" name="username" placeholder="管理员账号"/>
 
 <input class="el-input__inner" name="password" placeholder="密码"/>
 

(2)添加图标文件到 webapp 。 favicon.ico 防止 security 找不到而报错

(3)qingcheng_web_manager 工程 web.xml 添加配置 加入过滤器,这样才能处理所有的请求,由过滤器接管控制 此配置必须加在 springmvc 之前,否则没用

<filter>
      <filter-name>springSecurityFilterChain</filter-name>
      <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
      <filter-name>springSecurityFilterChain</filter-name>
      <url-pattern>/*</url-pattern>
  </filter-mapping>
 
  <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
 

(4)qingcheng_web_manager 工程添加配置文件 applicationContext_security.xml, 内容参照之前的 spring-security.xml , 添加配置 之前引入 security 过滤器接管请求权限,这里对其进行配置 将之前的原样复制 但是如果没有样式,也不行,因此应该对资源文件放行

<http pattern="/css/**" security="none"></http>
<http pattern="/img/**" security="none"></http>
<http pattern="/js/**" security="none"></http>
<http pattern="/*.ico" security="none"></http>
 
 

修改目标页地址为 main.html 而不是 index.html, 当登陆出错不是跳转错误页面而是回到登录页面

<form‐login login‐page="/login.html" default‐target‐url="/main.html"
authentication‐failure‐url="/login.html" />
 

(5)qingcheng_web_manager 工程添加 UserDetailsServiceImpl, 配置密码验证类

<beans:bean id="userDetailService" class="com.supersmart.controller.UserDetailServiceImpl"></beans:bean>
<beans:bean id="bcryptEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"></beans:bean>
 

开始登录

进入

3.2 访问数据库实现用户认证

3.2.1 表结构分析

项目系统库,负责管理员,权限,角色 系统数据库 qingcheng_system .tb_admin 表(管理员表)

数据库中存储的也并非密码

3.2.2 代码实现

实现根据用户名查找密码,创建接口,实现类,然后由 controller 调用服务 然而之前所用的代码生成器已经将方法自动生成 但是代码自动生成器对用户名查询使用了模糊搜索,用户名应该精确搜索,修改

// 用户名
            if(searchMap.get("loginName")!=null && !"".equals(searchMap.get("loginName"))){
//                criteria.andLike("loginName","%"+searchMap.get("loginName")+"%");
                criteria.andEqualTo("loginName",searchMap.get("loginName"));
            }
 

状态也改

 // 状态
            if(searchMap.get("status")!=null && !"".equals(searchMap.get("status"))){
//                criteria.andLike("status","%"+searchMap.get("status")+"%");
                criteria.andEqualTo("status",searchMap.get("status"));
            }
 

根据用户名从数据库中找到加密字符串参与用户密码的比对 并得到该用户的权限列表

修改 UserDetailsServiceImpl 查询管理员是否存在 此处角色依然定死

public class UserDetailServiceImpl implements UserDetailsService {
 
    @Reference
    private AdminService adminService;
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
 
        Map map = new HashMap<>();
        map.put("loginName", username);
        map.put("status", 1);
        List<Admin> list = adminService.findList(map);
        if (list.size()==0) {
            return null;
        }
        ArrayList<GrantedAuthority> grantedAuthorities = new ArrayList<GrantedAuthority>();
        grantedAuthorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        return new User(username,list.get(0).getPassword(),grantedAuthorities);
    }
}
 

运行项目 system 服务,webmanager 服务 进入登录界面

输入密码登录成功

4. 青橙系统菜单展示

后台界面分为两个部分,菜单区和功能区 横纵为菜单区,空白为功能区

4.1 需求分析

对于菜单也会涉及到增减,是动态的,应该从数据库中取出 背后是数据的海洋,前面是碧海蓝天 界面是由后台动态生成的 从数据库中读取菜单数据并展示。菜单为三级菜单, main.html

// 获取导航数据
 this.menuList=menu.data
   // 导航默认选择
   this.data=menu.data[1]
   let data=[]
for(let i=0;i<this.data.children.length;i++){
	data.push(this.data.children[i].path)
}
this.openeds=data
 

如果要实现动态的菜单,只需要移除本地固定的 menu.js,而访问后台数据返回一样格式的数据即可 menu.js 树形结构如下: path 相当于 id

{
"data": [
{
"path": "1",//菜单项所对应的路由路径
"title": "首页", //菜单项名称
"icon":"iconHome",//是否有子菜单,若没有,则为[]
},
{
"path": "2",
"title": "商品",//一级菜单
"icon":"iconCommodity",
"children": [
{
"path": "2‐1",
"title": "商品管理",//二级菜单
"linkUrl":"",
"icon":"iconSp",
"children":[
{
"path": "2‐1‐1",
"title": "商品列表",
"linkUrl":"all‐medical‐list.html",//三级菜单
},
{
"path": "2‐1‐2",
"title": "添加商品",
"linkUrl":"commodity‐add.html",
}
]
},
{
"path": "2‐2",
"title": "添加配置",
"linkUrl":"",
"icon":"iconSet",
"children":[
{
"path": "2‐2‐1",
"title": "商品分类",
"linkUrl":"all‐medical‐list.html",
},
{
"path": "2‐2‐2",
"title": "规格参数",
"linkUrl":"all‐medical‐list.html",
}
]
}
]
}
]
}
 

4.2 表结构分析

tb_menu (菜单表) id 对应 path name 对应 title icon 对应图标 url 对应 url 上级 id 是树状化的关键 为什么前后端定义名称不同呢? 加入中间层转换即可,随便怎么定义名称

表和菜单的需求分析至此结束

4.3 代码实现

4.3.1 后端代码

最终目标是生成前端所需要的的树状数据,包含结构与数据 (1)MenuService 接口新增方法定义,用于返回全部菜单 list 中的类型为什么呢? 因为涉及到了不存在的子节点,因此这里使用 map, 无法准确定义实体类 在此希望这个类能够返回所有菜单数据,并且结构为树状 即 data 所需的数据

public List<Map> findAllMenu();

(2)MenuServiceImpl 实现此方法 如何实现树状返回数据呢? 设计算法时应该注意,和数据库交互次数应该少,否则时间将会很长 因此应该一次将所有数据吸收,然后操作这些数据,不再与数据库交互 同时要根据用户的权限确定返回的菜单项 算法: 递归算法,此次使用 假如一共有 30 行数据,从顶级 0 分类开始寻找子类,比如依次找到 1 然后以 1 开始,遍历 30 行数据种的某些行(假如没有遍历完),寻找 1 的子类 1.1 再以 1.1 开始遍历 30 行数据(假如没有遍历完),寻找 1.1 的子类 1.1.1 再以 1.1.1 开始遍历 30 行数据(假如遍历完),寻找子类发现没有了,此时开始回退,此时已经有了 0-1-1.1-1.1.1 的链子 寻找到 1.1 的子类开始遍历 30 行数据(假如遍历完),找到 1.2 再以 1.2 开始… 如此建立树形结构

/**
 * 查询全部菜单
 * @return
 */
public List<Map> findAllMenu() {
        List<Menu> menuList = findAll();//查询全部菜单列表
        return findMenuListByParentId(menuList,"0");//一级菜单列表
        }
/**
 * 查询下级菜单ID
 * @param menuList
 * @param parentId
 * @return
 */
private List<Map> findMenuListByParentId(List<Menu> menuList,String
        parentId){
        List<Map> mapList=new ArrayList<>();
        for(Menu menu:menuList){ //循环一级菜单
        if(menu.getParentId().equals(parentId)){
        Map map=new HashMap();
        map.put("path",menu.getId());
        map.put("title",menu.getName());
        map.put("icon",menu.getIcon());
        map.put("linkUrl",menu.getUrl());
        map.put("children",findMenuListByParentId(menuList,menu.getId()));
        mapList.add(map);
        }
        }
        return mapList;
        }
 

(3)qingcheng_web_manager 工程 MenuController 新增方法

@GetMapping("/findMenu")
public List<Map> findMenu(){
        return menuService.findAllMenu();
        }
 

4.3.2 前端代码

原先的数据

 // 获取导航数据
this.menuList=menu.data
 

来自于静态的对象 js

<script src="js/menu.js"></script><!-- 导航菜单 -->
 

现在修改为动态数据 修改页面的 JS 代码 此处 response 就是回调的数据,

created() {
//.......
axios.get("/menu/findMenu.do").then(response=> {
                // 获取导航数据
                this.menuList=response.data
                // 导航默认选择
                this.data=response.data[1]
                let data=[]
                for(let i=0;i<this.data.children.length;i++){
                    data.push(this.data.children[i].path)
                }
                this.openeds=data
            })
 

此时登录系统就会发现 前端已经将数据吸收

4.3.3 同源策略设置

由于我们的 main.html 是框架页,需要修改同源策略。

在 scurity 中修改

<!--同源策略-->
        <headers>
            <frame-options policy="SAMEORIGIN"></frame-options>
        </headers>
 

response header 可用于指示是否应该允许浏览器呈现在一个页面 (同源策略) 同源策略限制了从同一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这 是一个用于隔离潜在恶意文件的重要安全机制。 解释一下同源。如果两个 url,协议、地址和端口都相同,我们称两个 url 为同源。 此时流量统计的 url 和当前地址端口,https 协议,地址都相同 必须三者相同才能称为同源,此时加载百度等为不同源

Spring Security 下,X-Frame-Options 默认为 DENY. 如果不修改同源策略,框架页内将 无法显示内容。 DENY:浏览器拒绝当前页面加载任何 Frame 页面 SAMEORIGIN:frame 页面的地址只能为同源域名下的页面 ALLOW-FROM:origin 为允许 frame 加载的页面地址。 此时页面已经放行

4.3.4 获取当前登录人

后台主界面还有一个问题未解决 登录人应该是动态的 在这里插入图片描述

需求:主界面显示当前登陆人 实现思路:后端编写 controller 输出当前登录人,前端异步调用。 (1)后端代码实现: 使用安全框架提供的上下文调用,得到登录人的名字

@RestController
@RequestMapping("/login")
public class LoginController {
    @GetMapping("/name")
    public Map showName() {
        String name = SecurityContextHolder.getContext().getAuthentication().getName();
        Map map = new HashMap();
        map.put("name", name);
        return map;
 
    }
}
 

(2)前端代码实现: 调用登录的人的名字 在 main.html 中添加属性 loginName,用于存储当前的登陆人

 data() {
        return {
            visible: false,
            isCollapse: false,
            tabWidth: 180,
            test1: 1,
            intelval: null,
            winfo:'winfo',
            data:[],
            menuList:[],
            defaultActiveIndex: "2",
            defaultActive:'2-1-2',
            openeds: [],
            linkUrl:'all-item-list.html',
            loginName:""
            }
        },
 

created() 方法添加代码 前端得到登录人的名字

 //加载并显示当前登录名
            axios.get('/login/name.do').then( response=>{
                this.loginName= response.data.name;
            })
 

页面显示登陆人 在人名显示的地方登录

{{loginName}}
 
 

此时人名已经根据登陆人名自动动态显示了

4.3.5 退出登录

当我们在 spring security 配置文件中配置了 后,框架会为我们自动提供 退出功能,地址为 / logout,要求以 post 方式提交。 浏览器直接输入会 404 main.html 新增方法 不仅要退出,还要回到登录界面

 exit(){
                axios.post('/logout').then(response=>{
                    location.href="login.html";
                })
            }
 
 

退出菜单调用: 通过退出按钮调用

<span style="display:block;" @click="exit()">退出</span>
 

这种退出如果需要访问后台还需要重新登陆

5. 管理员登录日志

5.1 需求分析

管理员对于系统影响巨大,因此管理员的操作应该留下痕迹

管理员登录后,记录管理员名称、登录时间、ip、浏览器类型、所在地区等信息 。

5.2 表结构分析

tb_login_log 表

5.3 代码实现

5.3.1 登录成功处理器

(1)spring security 为我们提供了一个叫 “登录成功处理器” 的组件,我们可以实现在登 录后进行的后续处理逻辑。

但是如果配置了登陆成功处理器,default-target-url=“/main.html” 就会失效,因为不再默认跳转了,已经覆盖默认 因此使用重定向即可 这里使用了其提供的前两个对象,rquest 和 response

public class AuthenticationSuccessHanderImpl implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        System.out.println("登录成功处理器检测到登录成功");
        httpServletRequest.getRequestDispatcher("/main.html").forward(httpServletRequest,httpServletResponse);
    }
}
 
 

(2)applicationContext_security.xml 新增配置

<beans:bean id="loginHandler" class="com.supersmart.controller.AuthenticationSuccessHanderImpl"></beans:bean>
 
 
<!‐‐设置登陆成功处理器‐‐>
<form‐login login‐page="/login.html"
default‐target‐url="/main.html"
        authentication‐failure‐url="/login.html"
        authentication‐success‐handler‐ref="loginHandler"/>
 

此时既能登陆到主界面也能捕捉登陆成功信息

5.3.2 登录日志处理

修改 qingcheng_web_manager 的 AuthenticationSuccessHandlerImpl 使用认证对象获得信息 使用 ip 获得地址信息与获得浏览器信息暂时为空 注意 loginlog 数据对象与服务方法的创建

 @Reference
    private LoginLogService loginLogService;
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        System.out.println("登录成功处理器检测到登录成功");
        String loginName = authentication.getName();
        String ip = httpServletRequest.getRemoteAddr();
        LoginLog loginLog = new LoginLog();
        loginLog.setLoginName(loginName);
        loginLog.setLoginTime(new Date());
        loginLog.setIp(ip);
        loginLog.setLocation("");
        loginLog.setBrowserName("");
 
        loginLogService.add(loginLog);
        httpServletRequest.getRequestDispatcher("/main.html").forward(httpServletRequest,httpServletResponse);
    }
 
 

5.3.3 根据 IP 获取城市信息

(1)将工具类 WebUtil 复制到 qingcheng_common_web 工程 (2)WebUtil 类的 getCityByIP 用于根据 IP 地址获取城市信息,修改代码 此工具类需要依赖网络,调用 baidu api,返回 ip 数据

loginLog.setLocation(WebUtil.getCityByIP(ip));//保存城市信息
 
 

5.3.4 获取浏览器名称

String agent = httpServletRequest.getHeader("user‐agent");
 

获取到的信息如下: agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.190 Safari/537.36 没有必要存这么长的字符串,我们只需要存储一个浏览器名称就可以了,所以我们 用到了 WebUtil 给我们提供的方法 getBrowserName(String agent)

当然,需要注意的是写好 loginLog 方法,数据对象,接口,实现方法,controller,否则这里无法调用 当然软件自动生成器已经自动生成

 @Reference
    private LoginLogService loginLogService;
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        System.out.println("login check!");
        String loginName = authentication.getName();
        String ip = httpServletRequest.getRemoteAddr();
        LoginLog loginLog = new LoginLog();
        loginLog.setLoginName(loginName);
        loginLog.setLoginTime(new Date());
        loginLog.setIp(ip);
        loginLog.setLocation(WebUtil.getCityByIP(ip));
        String header = httpServletRequest.getHeader("user-agent");
 
        loginLog.setBrowserName(WebUtil.getBrowserName(header));
 
        loginLogService.add(loginLog);
        httpServletRequest.getRequestDispatcher("/main.html").forward(httpServletRequest,httpServletResponse);
    }
 

此时日志已经进入日志表

5.3.5 登录日志列表

希望可以查询到管理员登录的日志列表 需求:查询我的登录日志列表 实现思路:后端获取当前登录人账号作为查询条件。前端精简生成的增删改查代码 思路是根据登录的鉴权信息,找到用户名,然后根据用户名查询日志表

/**
 * 查询当前登录人的登录日志
 * @param page
 * @param size
 * @return
 */
 @GetMapping("/findPageByLogin")
    public PageResult<LoginLog> findPageByLogin(int page, int size){
        //添加条件
        String loginName = SecurityContextHolder.getContext().getAuthentication().getName();
        Map map=new HashMap();
        map.put("loginName",loginName);
        return loginLogService.findPage(map,page,size);
    }
 

如图已经登录查看日志

6. 修改密码

6.1 需求分析

需求:界面输入原密码,新密码,确认密码。 新密码和确认密码必须一致,在前端校 验。提交给后端后,后端判断原密码是否正确,如果正确,修改密码,注意密码要使用 BCrypt 加密。

6.2 思路

这里我们会使用两个知识点: (1)获取当前登陆人。 (2)BCrypt 校验密码与加密。 …