今日目标:
(1)实现 Spring Security 入门 Demo
(2)完成运营商登录与安全控制功能
(3)完成商家入驻
(4)完成商家审核
(5)完成商家系统登录与安全控制功能
目录
1、运营商系统登录与安全控制
1.1 导入 Spring Security 依赖
<!-- spring security -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
1.2 配置文件相关
(1)web.xml 新增配置
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/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>
(2)新增spring-security.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 pattern="/login.html" security="none"></http>
<http pattern="/css/**" security="none"></http>
<http pattern="/img/**" security="none"></http>
<http pattern="/js/**" security="none"></http>
<http pattern="/plugins/**" security="none"></http>
<!-- 页面拦截规则 -->
<http use-expressions="false">
<intercept-url pattern="/**" access="ROLE_ADMIN" />
<form-login login-page="/login.html" default-target-url="/admin/index.html"
authentication-failure-url="/login.html" always-use-default-target="true"/>
<csrf disabled="true"/>
<headers>
<frame-options policy="SAMEORIGIN"/>
</headers>
</http>
<!-- 认证管理器 -->
<authentication-manager>
<authentication-provider>
<user-service>
<user name="admin" password="123456" authorities="ROLE_ADMIN"/>
<user name="user" password="123456" authorities="ROLE_ADMIN"/>
</user-service>
</authentication-provider>
</authentication-manager>
</beans:beans>
(3)指定登录页面,访问的action路径为Spring Security提供的/login,并配置账号密码提交的字段为username和password
注意:提交路径,和name属性的值都是可以在配置文件中修改的,都可以在form-login 的属性中配置
<!--
login-processing-url="/sysLogin" : 配置登录请求的路径
username-parameter="user" : 配置账号提交到的字段
password-parameter="pwd" : 配置密码提交到的字段
-->
(4)指定表单id,并给登录按钮设置绑定事件,提交表单
注意:
(1)表单提交必须为post
(2)提交路径、账号和密码字段,均可以自定义
(3)登录成功默认是跳转到本次会话的上一次没有访问成功的页面,如果没有就跳转到默认登录成功页面,always-user-default-target="true"配置,可以设置,登陆成功总是跳转到默认登录成功页面,一般后台管理系统会配置。前台页面不配置,用户体验会更好。
1.3 登录后显示登录用户名
(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<String, String> 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}}" ,使用查找替换
效果:
1.4 退出登录
只需要在 spring-security中的http节点中,配置 logout ,然后在前端页面中的注销按钮,请求该 /logout 即可
(1)配置
(2)注销按钮
2、商家申请入驻
2.1 前端
(1)为所有的输入框绑定提交变量
(2)给申请入驻按钮绑定单击事件
(3)修改前端新增 JS 代码
2.2 后端
只需要在保存之前,补全数据即可(sellergoods-service)
3、商家审核
3.1 待审核商家列表
(1)引入js,在页面添加分页控件,在body中引入 ng-app 和 ng-controller
(2)循环显示列表
(3)初始化的时候,设置搜索status=0
3.2 查看商家详情
(1)为详情按钮添加单击事件
(2)绑定变量到需要回显数据的地方
3.3 商家状态修改
(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)前端按钮添加单击事件
4、商家系统登录和安全控制
4.1 准备工作
(1)引入Spring Security依赖
(2)修改web.xml
(3)修改登录表单。提交路径为"/login";账号和密码提交的字段分别为 username 和password;给按钮添加单击事件,用于提交登录表单数据
4.2 商家登录
(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<GrantedAuthority> 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配置文件
<?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"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
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://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
<!-- 以下页面不被拦截 -->
<http pattern="/*.html" security="none"></http>
<http pattern="/css/**" security="none"></http>
<http pattern="/img/**" security="none"></http>
<http pattern="/js/**" security="none"></http>
<http pattern="/plugins/**" security="none"></http>
<http pattern="/seller/add.do" security="none"></http>
<!-- 页面拦截规则 -->
<http use-expressions="false">
<intercept-url pattern="/**" access="ROLE_SELLER" />
<!--
login-processing-url="/sysLogin" : 配置登录请求的路径
username-parameter="user" : 配置账号提交到的字段
password-parameter="pwd" : 配置密码提交到的字段
always-use-default-target :
总是跳转到默认的登录成功后显示的页面,如果不写这个配置,
默认登录成功后首先跳转到当前会话上次没有访问成功的页面
-->
<form-login login-page="/shoplogin.html" default-target-url="/admin/index.html"
authentication-failure-url="/shoplogin.html" always-use-default-target="true"/>
<!-- 退出登录 -->
<logout />
<csrf disabled="true"/>
<!-- 配置ifream允许访问 -->
<headers>
<frame-options policy="SAMEORIGIN"/>
</headers>
</http>
<!-- 认证管理器 -->
<authentication-manager>
<!-- 指定自定认证类为认证提供者 -->
<authentication-provider user-service-ref="userDetailsService"/>
</authentication-manager>
<!-- 配置自定义认证类 -->
<beans:bean id="userDetailsService" class="com.pinyougou.shop.security.UserDetailsServiceImpl">
<beans:property name="sellerService" ref="sellerService"/>
</beans:bean>
<!-- 引用dubbo 服务 -->
<dubbo:application name="pinyougou-shop-web" />
<dubbo:registry address="zookeeper://192.168.25.170:2181"/>
<dubbo:reference id="sellerService" interface="com.pinyougou.sellergoods.service.SellerService"/>
</beans:beans>
4.3 BCrypt加密算法
用户表的密码通常使用MD5等不可逆算法加密后存储,为防止彩虹表破解更会先使用一个特定的字符串(如域名)加密,然后再使用一个随机的salt(盐值)加密。 特定字符串是程序代码中固定的,salt是每个密码单独随机,一般给用户表加一个字段单独存储,比较麻烦。 BCrypt算法将salt随机并混入最终加密后的密码,验证时也无需单独提供之前的salt,从而无需单独处理salt问题。
4.4 商家入驻时,进行密码加密
(1)修改SellerController的add方法(shop-web)
(2)在spring-security.xml配置文件中,配置登录时的密码加密方式
4.5 商家管理与商家审核一致,参考商家审核
5、商家修改资料
5.1 回显数据到修改资料页面
(1)后端,LoginController(shop-web),新增方法获取当前登录用户的id
/**
* 返回当前登录用户ID
*/
@RequestMapping("/sellerId")
public String sellerId() {
// 使用spring security的方法获取
String name = SecurityContextHolder.getContext().getAuthentication().getName();
return name;
}
5.2 前端
(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)效果
5.2 点击保存,修改资料(后端部分已由代码生成器生成)
(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)为页面中的 保存按钮绑定单击事件
6、商家修改密码
6.1 后端
(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("修改密码失败");
}
}
6.2 前端
(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)保存按钮绑定单击事件