今日目標:
(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)保存按鈕綁定單擊事件