能够使用BCrypt进行密码加密
完成 Spring Security入门
完成青橙登录认证
完成青橙菜单展示
完成用户登录日志
项目序列-7:https://github.com/Jonekaka/https-github.com-Jonekaka-javaweb-qingcheng-7-82.git
用户输入账户密码,密码信息被加密后传送,和数据库中的加密数据比对
即使信息被拦截也无法逆运算出原来的密码
随着技术的发展,md5已经不安全了,很多人会撞库,将所有的可能的密码的md5存储起来,如果有相同的就能解密了
期间并没有运算,而是查询
新的思路是,即是字符串一样,但是计算出的加密字符串不同,加入“随机盐”,就像佐料一样
目前,MD5和BCrypt比较流行。相对来说,BCrypt比MD5更安全。
BCrypt 官网http://www.mindrot.org/projects/jBCrypt/
(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);
没有安全框架之前的登录验证
使用session保存登录信息,为空则强制跳转登录页面
什么是安全框架?
解决系统安全问题的框架。管理访问控制权限,通过配置的方式实现对资源的访问限制。
Spring Security:spring家族一员。是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。
它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control
,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。
Apache Shiro 是一个功能强大且易于使用的Java安全框架,提供了认证,授权,加密,和会话管理。
认证:限制用户只能登陆才可以访问资源。
授权:限制用户必须有某资源的访问权限才可以访问。
这里主要解释认证,授权会在下一次介绍中详细介绍
需求:实现简单的登陆,当用户没有登陆访问主页执行拦截跳转到登陆,登陆后跳转到
主页。实现退出登陆的功能,退出后再次访问主页仍然拦截。
为了简化登录环节,这里
用户名和密码不连接数据
库,直接在配置文件中配置。
(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’)决定什么角色可以访问
<?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 为开启表单登陆
修改配置文件中的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"
/>
(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,所以将此功能关闭。一般静态页采用图形验证码的方式实现防止跨域请求伪造的功
能。
刚才是将用户名和密码配置在配置文件中,
实际的是从数据库中提取用户名和密码信息,如何做到呢?
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"/>
此时登录认证
实现管理后台的登录功能。登录页采用前端提供的页面。
登录
后台主界面
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>
项目系统库,负责管理员,权限,角色
系统数据库qingcheng_system .tb_admin 表(管理员表)
数据库中存储的也并非密码
实现根据用户名查找密码,创建接口,实现类,然后由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服务
进入登录界面
输入密码登录成功
后台界面分为两个部分,菜单区和功能区
横纵为菜单区,空白为功能区
对于菜单也会涉及到增减,是动态的,应该从数据库中取出
背后是数据的海洋,前面是碧海蓝天
界面是由后台动态生成的
从数据库中读取菜单数据并展示。菜单为三级菜单,
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",
}
]
}
]
}
]
}
tb_menu (菜单表)
id对应path
name对应title
icon对应图标
url对应url
上级id是树状化的关键
为什么前后端定义名称不同呢?
加入中间层转换即可,随便怎么定义名称
最终目标是生成前端所需要的的树状数据,包含结构与数据
(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();
}
原先的数据
// 获取导航数据
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
})
由于我们的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加载的页面地址。
此时页面已经放行
后台主界面还有一个问题未解决
登录人应该是动态的
![在这里插入图片描述](https://img-blog.csdnimg.cn/20210430210550721.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nL mNzZG4ubmV0L2xpZGFzaGVudA==,size_16,color_FFFFFF,t_70)
需求:主界面显示当前登陆人
实现思路:后端编写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}}
当我们在spring security配置文件中配置了 后,框架会为我们自动提供
退出功能,地址为/logout,要求以post方式提交。
浏览器直接输入会404
main.html新增方法
不仅要退出,还要回到登录界面
exit(){
axios.post('/logout').then(response=>{
location.href="login.html";
})
}
退出菜单调用:
通过退出按钮调用
<span style="display:block;" @click="exit()">退出</span>
管理员对于系统影响巨大,因此管理员的操作应该留下痕迹
管理员登录后,记录管理员名称、登录时间、ip、浏览器类型、所在地区等信息 。
tb_login_log 表
(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"/>
此时既能登陆到主界面也能捕捉登陆成功信息
修改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);
}
(1)将工具类WebUtil复制到qingcheng_common_web工程
(2)WebUtil类的getCityByIP用于根据IP地址获取城市信息,修改代码
此工具类需要依赖网络,调用baidu api,返回ip数据
loginLog.setLocation(WebUtil.getCityByIP(ip));//保存城市信息
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);
}
希望可以查询到管理员登录的日志列表
需求:查询我的登录日志列表
实现思路:后端获取当前登录人账号作为查询条件。前端精简生成的增删改查代码
思路是根据登录的鉴权信息,找到用户名,然后根据用户名查询日志表
/**
* 查询当前登录人的登录日志
* @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);
}
需求:界面输入原密码,新密码,确认密码。 新密码和确认密码必须一致,在前端校
验。提交给后端后,后端判断原密码是否正确,如果正确,修改密码,注意密码要使用
BCrypt加密。
这里我们会使用两个知识点:
(1)获取当前登陆人。
(2)BCrypt校验密码与加密。
…