今日目标:
(1)实现 Spring Security 入门 Demo
(2)完成运营商登录与安全控制功能
(3)完成商家入驻
(4)完成商家审核
(5)完成商家系统登录与安全控制功能
目录
1、运营商系统登录与安全控制
1.1 导入 Spring Security 依赖
1.2 配置文件相关
1.3 登录后显示登录用户名
1.4 退出登录
2、商家申请入驻
2.1 前端
2.2 后端
3、商家审核
3.1 待审核商家列表
3.2 查看商家详情
3.3 商家状态修改
4、商家系统登录和安全控制
4.1 准备工作
4.2 商家登录
4.3 BCrypt加密算法
4.4 商家入驻时,进行密码加密
4.5 商家管理与商家审核一致,参考商家审核
5、商家修改资料
5.1 回显数据到修改资料页面
5.2 前端
5.2 点击保存,修改资料(后端部分已由代码生成器生成)
6、商家修改密码
6.1 后端
6.2 前端
org.springframework.security
spring-security-web
org.springframework.security
spring-security-config
(1)web.xml 新增配置
contextConfigLocation
classpath:spring/spring-security.xml
org.springframework.web.context.ContextLoaderListener
springSecurityFilterChain
org.springframework.web.filter.DelegatingFilterProxy
springSecurityFilterChain
/*
(2)新增spring-security.xml 文件
(3)指定登录页面,访问的action路径为Spring Security提供的/login,并配置账号密码提交的字段为username和password
注意:提交路径,和name属性的值都是可以在配置文件中修改的,都可以在form-login 的属性中配置
(4)指定表单id,并给登录按钮设置绑定事件,提交表单
注意:
(1)表单提交必须为post
(2)提交路径、账号和密码字段,均可以自定义
(3)登录成功默认是跳转到本次会话的上一次没有访问成功的页面,如果没有就跳转到默认登录成功页面,always-user-default-target="true"配置,可以设置,登陆成功总是跳转到默认登录成功页面,一般后台管理系统会配置。前台页面不配置,用户体验会更好。
(1)后端代码,新建一个LoginController,用于获取登录名并返回到前端
package com.pinyougou.manager.controller;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
/**
* 登录相关控制层
* Author xushuai
* Description
*/
@RestController
@RequestMapping("/login")
public class LoginController {
/**
* 返回当前登录用户名
*
* @return java.util.Map
*/
@RequestMapping("/showName")
public Map showName() {
// 使用spring security的方法获取
String name = SecurityContextHolder.getContext().getAuthentication().getName();
// 封装到 Map 中
Map map = new HashMap<>();
map.put("loginName", name);
return map;
}
}
(2)前端
a、编写loginService.js
app.service('loginService', function ($http) {
//获取登录用户名
this.showName = function () {
return $http.get('../login/showName.do');
}
});
b、编写indexService.js
app.controller('indexController', function ($scope, loginService) {
// 显示当前登录用户名
$scope.showName = function () {
loginService.showName().success(
function (rtn) {
$scope.loginName = rtn.loginName;
}
);
}
});
c、页面引入js文件
d、修改所有 "测试用户" 为 "{{loginName}}" ,使用查找替换
效果:
只需要在 spring-security中的http节点中,配置 logout ,然后在前端页面中的注销按钮,请求该 /logout 即可
(1)配置
(2)注销按钮
(1)为所有的输入框绑定提交变量
(2)给申请入驻按钮绑定单击事件
(3)修改前端新增 JS 代码
只需要在保存之前,补全数据即可(sellergoods-service)
(1)引入js,在页面添加分页控件,在body中引入 ng-app 和 ng-controller
(2)循环显示列表
(3)初始化的时候,设置搜索status=0
(1)为详情按钮添加单击事件
(2)绑定变量到需要回显数据的地方
(1)服务层接口(sellergoods-interface),新增方法
/**
* 修改商家状态
*
* @param sellerId 商家id
* @param status 状态
*/
void updateStatus(String sellerId, String status);
(2)服务层实现(sellergoods-service),实现
@Override
public void updateStatus(String sellerId, String status) {
//查询商家
TbSeller seller = sellerMapper.selectByPrimaryKey(sellerId);
if(seller != null) {
//修改状态
seller.setStatus(status);
//保存
sellerMapper.updateByPrimaryKey(seller);
}
}
(3)控制层(SellerController)
/**
* 修改商家状态
*
* @return entity.Result
*/
public Result updateStatus(String sellerId, String status) {
try {
sellerService.updateStatus(sellerId, status);
return Result.success("修改成功");
} catch (Exception e) {
e.printStackTrace();
return Result.error("修改失败");
}
}
(4)前端sellerService.js新增方法
//更改状态
this.updateStatus = function (sellerId, status) {
return $http.get('../seller/updateStatus.do?sellerId=' + sellerId + '&status=' + status);
}
(5)前端sellerController.js新增方法
//修改商家状态
$scope.updateStatus = function (sellerId, status) {
sellerService.updateStatus(sellerId,status).success(
function (rtn) {
alert(rtn.message);
if(rtn.success) {
$scope.reloadList();//刷新列表
}
}
);
}
(6)前端按钮添加单击事件
(1)引入Spring Security依赖
(2)修改web.xml
(3)修改登录表单。提交路径为"/login";账号和密码提交的字段分别为 username 和password;给按钮添加单击事件,用于提交登录表单数据
(1)编写自定义认证类,需要实现 UserDetailsService
package com.pinyougou.shop.security;
import com.pinyougou.pojo.TbSeller;
import com.pinyougou.sellergoods.service.SellerService;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import java.util.ArrayList;
import java.util.List;
/**
* Spring Security 自定义认证类
* Author xushuai
* Description
*/
public class UserDetailsServiceImpl implements UserDetailsService {
private SellerService sellerService;
public void setSellerService(SellerService sellerService) {
this.sellerService = sellerService;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// 构建角色列表
List authorities = new ArrayList<>();
// 这个角色名必须在 Spring Security 配置文件中配置
authorities.add(new SimpleGrantedAuthority("ROLE_SELLER"));
//按用户名获取商家
TbSeller seller = sellerService.findOne(username);
if (seller != null) {
// 判断商家状态是否合法
if(seller.getStatus().equals(TbSeller.STATUS_CHECK)) {// 合法
/*
* 进行校验:
* Spring Security会自动校验输入的username、password,与User对象中的useranme和password进行校验
* 如果校验成功,就将角色列表中的角色赋予给当前登录的用户
*/
return new User(username, seller.getPassword(), authorities);
}
}
return null;
}
}
(2) spring-security.xml配置文件
用户表的密码通常使用MD5等不可逆算法加密后存储,为防止彩虹表破解更会先使用一个特定的字符串(如域名)加密,然后再使用一个随机的salt(盐值)加密。 特定字符串是程序代码中固定的,salt是每个密码单独随机,一般给用户表加一个字段单独存储,比较麻烦。 BCrypt算法将salt随机并混入最终加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理salt问题。
(1)修改SellerController的add方法(shop-web)
(2)在spring-security.xml配置文件中,配置登录时的密码加密方式
(1)后端,LoginController(shop-web),新增方法获取当前登录用户的id
/**
* 返回当前登录用户ID
*/
@RequestMapping("/sellerId")
public String sellerId() {
// 使用spring security的方法获取
String name = SecurityContextHolder.getContext().getAuthentication().getName();
return name;
}
(1)引入js文件,设置ng-app和ng-controller
(2)输入框绑定变量,回显数据
(3)loginService.js新增方法
this.sellerId = function () {
return $http.get('../login/sellerId.do');
}
(4)sellerService.js新增方法
// 使用id加载当前商家信息
$scope.sellerId = "";
$scope.loadId = function () {
loginService.sellerId().success(
function (rtn) {
sellerId = JSON.parse(rtn);
$scope.findOne(sellerId);
}
);
}
注意:需要注入loginService服务,且前端页面要引入loginService.js文件
(5)页面初始化运行 loadId()
(6)效果
(1)前端,sellerController.js新增方法
//更新
$scope.update=function(){
sellerService.update( $scope.entity ).success(
function(response){
if(response.success){
alert(response.message);
$scope.loadId();
}else{
alert(response.message);
}
}
);
}
(2)为页面中的 保存按钮绑定单击事件
(0)新增一个实体类,用于接受前端传过来的新旧密码
package entity;
/**
* 修改密码时,存放旧密码和新密码的实体
* Author xushuai
* Description
*/
public class Password {
private String oldPwd;
private String newPwd;
public String getOldPwd() {
return oldPwd;
}
public void setOldPwd(String oldPwd) {
this.oldPwd = oldPwd;
}
public String getNewPwd() {
return newPwd;
}
public void setNewPwd(String newPwd) {
this.newPwd = newPwd;
}
}
(1)服务层接口(sellergoods-interface),新增方法
/**
* 修改密码
*
* @param sellerId 商家id
* @param oldPwd 旧密码
* @param newPwd 新密码
*/
void updatePassword(String sellerId, String newPwd);
(2)服务层实现(sellergoods-service),实现
@Override
public void updatePassword(String sellerId, String newPwd) {
// 查询商家
TbSeller seller = sellerMapper.selectByPrimaryKey(sellerId);
if(seller != null) {
// 修改密码
seller.setPassword(newPwd);
sellerMapper.updateByPrimaryKey(seller);
}
}
(3)控制层,shop-web下的SellerController(重点是使用 BCrypt.checkpw() 进行密码校验)
@RequestMapping("/updatePassword")
public Result updatePassword(@RequestBody Password password) {
try {
// 对密码进行加密处理
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String newPwd = passwordEncoder.encode(password.getNewPwd());
//获取当前登录的用户id
String name = SecurityContextHolder.getContext().getAuthentication().getName();
TbSeller seller = findOne(name);
//校验两个密码是否一致
if(BCrypt.checkpw(password.getOldPwd(),seller.getPassword())) {//一致
sellerService.updatePassword(name, newPwd);
return Result.success("修改密码成功");
}
return Result.error("原密码错误");
} catch (Exception e) {
e.printStackTrace();
return Result.error("修改密码失败");
}
}
(1)引入js相关
(2)绑定变量到输入框
(3) sellerController.js新增方法
// 修改密码
$scope.updatePassword = function () {
//校验两次密码是否一致
if($scope.newPwd != $scope.newPwd1) {
alert("两次密码输入不一致!");
} else {
$scope.password={oldPwd:$scope.oldPwd,newPwd:$scope.newPwd};
sellerService.updatePassword($scope.password).success(
function (rtn) {
alert(rtn.message);
}
);
}
}
(4)sellerService.js新增方法
//修改密码
this.updatePassword = function (password) {
return $http.post('../seller/updatePassword.do', password);
}
(5)保存按钮绑定单击事件