電商平臺的搭建(SpringMVC+SpringSecurity/Validation+Redis+MySQL+React)----登陸註冊模組
工作之餘搭建的電商平臺,現已存在的功能有:
1)使用者註冊登陸
2)購物車功能(未登入購物車商品儲存cookie,登陸儲存redis)
3)訂單填寫確認功能
4)訂單狀態查詢功能
技術實現:
使用SpringMVC架構 maven包管理
1)SpringSecurity/Validation完成使用者登陸註冊驗證及反饋
2)購物車由於更新頻繁,使用二級快取redis來儲存使用者登陸後的購物車資訊,未登入狀態下的購物車資訊儲存到瀏覽器cookie,中間登陸會把cookie中的購物車更新到redis並清除cookie
3)資料持久化經hibernate到MySQL
4)React構建部分頁面(譬如使用者登入後用戶資訊目前儲存到session中,這一塊在前端用jsp實現,故這部分頁面是jsp+react混用)
主要用到的技術就是上邊這樣,當然像jquery/bootstrap/css這些專案中肯定也是必須的
已提交到github,地址
一、maven的pom.xml引入專案的依賴
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion> 4.0.0</modelVersion>
<groupId>com.git.postgraduate</groupId>
<artifactId>bookstore</artifactId>
<packaging>war</packaging>
<version>0.0.1-SNAPSHOT</version>
<name>bookstore Maven Webapp</name>
<url>http://maven.apache.org</url >
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.spring.platform</groupId>
<artifactId>platform-bom</artifactId>
<version>Brussels-RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
</dependency>
<!-- Hibernate -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
</dependency>
<!-- DBCP connection pool -->
<dependency>
<groupId>commons-dbcp</groupId>
<artifactId>commons-dbcp</artifactId>
</dependency>
<dependency>
<groupId>commons-pool</groupId>
<artifactId>commons-pool</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Servlet -->
<!-- https://mvnrepository.com/artifact/javax.servlet/servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
</dependency>
<dependency>
<groupId>commons-validator</groupId>
<artifactId>commons-validator</artifactId>
<version>1.5.0</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
</dependencies>
<build>
<finalName>bookstore</finalName>
</build>
</project>
以上為引入的專案依賴包
先對其中幾個點略作介紹
1、dependencyManagement 負責管理包的version,下邊的dependency不需要再填寫version
2、spring的核心部分:webmvc/orm
3、hibernate部分:hibernate-core/hibernate-entitymanager
4、mysql部分:mysql-connector-java
5、jsp/servlet部分:servlet-api/jsp-api/jstl
6、security部分:spring-security-web/spring-security-config
7.validation部分:commons-validator
8.redis部分:spring-data-redis/jedis
二、登入模組
先看配置部分:
1、security-context.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans>
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="userDetailsServiceImpl">
<password-encoder ref="encoder"></password-encoder>
</authentication-provider>
</authentication-manager>
<beans:bean id="userDetailsServiceImpl" class="git.com.postgraduate.bookstore.service.UserDetailsServiceImpl"></beans:bean>
<beans:bean id="encoder"
class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder">
<beans:constructor-arg name="strength" value="11"/>
</beans:bean>
</beans:beans>
authenticationManager作為認證管理中心,以userDetailsServiceImpl為認證來源,密碼編碼處理用BCrypt處理,防止密碼以明文形式傳遞
來看下userDetailsServiceImpl:
package git.com.postgraduate.bookstore.service;
//packages dependency ignore
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private AccountDAO accountDAO;
@Transactional(readOnly= true)
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Account account = accountDAO.findAccount(username);
System.out.println("Account=" + account);
if(account == null) {
throw new UsernameNotFoundException("User" + username + "was not found in the database");
}
//EMPLOYEE MANAGER
String role = account.getUserRole();
List<GrantedAuthority> grantList = new ArrayList<GrantedAuthority>();
// ROLE_EMPLOYEE ROLE_MANAGER
GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + role);
grantList.add(authority);
boolean enabled = account.isActive();
boolean accountNonExpired = true;
boolean credentialsNonExpired = true;
boolean accountNonLocked = true;
UserDetails userDetails = (UserDetails)new User(account.getUserName(), account.getPassword(), enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, grantList);
return userDetails;
}
}
大致意思是:根據使用者提供的userName從user資料庫中取出該使用者名稱所匹配的使用者真實資訊(這裡以userName作為primary key,唯一),將user 真實資訊(username/password/userRole/…)返回給認證管理中心,注意,該類實現了UserDetailsService介面,該介面屬於springSecurity
除了xml配置security外,還有另外一個java類作為config類(當然可以完全用xml配置或用java類配置),該類主要作用是設定哪些頁面訪問需要許可權formLogin登陸驗證成功或失敗頁面如何跳轉
package git.com.postgraduate.bookstore.config;
@EnableWebSecurity
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
/*授權相關的在security-context.xml中已配置
* 包括provider和password encoder
* */
@Override
protected void configure(HttpSecurity http) throws Exception {
//TODO
http.csrf().disable();
//the pages requires login as EMPLOYEE or MANAGER.
//if not login, it will redirect to /login page
http.authorizeRequests().antMatchers("/orderList","/order","/accountInfo")
.access("hasRole('ROLE_EMPLOYEE', 'ROLE_MANAGER')");
http.authorizeRequests().antMatchers("/product").access("hasRole('RILE_MANAGER')");
//when the user has logged in as XX.
//But access a page that requires role YY,
//AccessDeniedException will throw
http.authorizeRequests().and().exceptionHandling().accessDeniedPage("/403");
http
.authorizeRequests()
.and()
.formLogin()
.loginPage("/login")
.loginProcessingUrl("/request_for_login")
.defaultSuccessUrl("/findbook")
.failureUrl("/login?error")
.usernameParameter("userName")
.passwordParameter("password")
.and().logout().logoutUrl("/logout").logoutSuccessUrl("/login?logout");
}
}
專門解釋下使用者登陸模組:
.loginProcessingUrl(“/request_for_login”) —form提交時的action path,即某表單提交的路徑是/request_for_login時攔截到此進行驗證
.defaultSuccessUrl(“/findbook”) — 驗證通過後預設的跳轉頁面
.failureUrl(“/login?error”) — 驗證失敗後的跳轉頁面,url後帶error,
.usernameParameter(“userName”)
.passwordParameter(“password”) —form提交過來的username和password與認證管理中心的使用者真實資訊進行匹配,從而驗證通過或失敗?(此處有待考證)
.logout().logoutUrl(“/logout”).logoutSuccessUrl(“/login?logout”) — 登出後跳轉到哪個頁面
另外還有一個service,算是一個工具類,提供查詢當前已通過使用者驗證的使用者資訊(這裡主要是為了將已登入過的使用者資訊顯示到前端)
package git.com.postgraduate.bookstore.service;
@Service
public class SecurityServiceImpl implements SecurityService {
public String findLoggedUsername() {
String username = SecurityContextHolder.getContext().getAuthentication().getName();
if(!username.equals("anonymousUser"))
return username;
return null;
}
}
OK,到這裡,基本上使用者的登陸模組基本完成了,還有已登入使用者的資訊跟蹤功能(SecurityServiceImpl )
當登陸失敗後返回登陸頁面,如何顯示錯誤資訊呢?
看一下:
@RequestMapping(value={"/login"}, method= RequestMethod.GET)
public String login(Model model,String error, String logout) {
if(error != null)
model.addAttribute("error", "Your username and password is invalid");
if(logout != null)
model.addAttribute("message", "You have been logged out successfully");
return "login";
}
ok,之前我們在WebSecurityConfig 配置了登陸失敗和登出後跳轉到/login,並分別帶過來error和logout,這樣就會將狀態資訊放入model傳到前端來顯示到底是登陸失敗還是登出成功啦
二、註冊模組
來來來,看一下注冊模組,學習的過程是不是讓人興奮?哈哈 當然我把各功能拆開來分析的,很多細節沒有分析到或者解析的不對,歡迎斧正。
這裡的註冊功能我就走正常的MVC模式了,request先mapping到controller(其實登陸模組主要是用了springSecurity來處理的,並沒有走MVC模式)
先看controller
package git.com.postgraduate.bookstore.controller;
@Controller
public class SecurityController {
@Autowired
private AccountService accountService;
@Autowired
private AccountValidator accountValidator;
@RequestMapping(value={"/registration"}, method= RequestMethod.GET)
public String Registration(Model model) {
model.addAttribute("accountForm", new Account());
return "registration";
}
@RequestMapping(value={"/registration"}, method= RequestMethod.POST)
public String registration(@ModelAttribute("accountForm") Account accountForm, BindingResult bindingResult, Model model) {
accountValidator.validate(accountForm, bindingResult);
if(bindingResult.hasErrors()) {
return "registration";
}
accountService.save(accountForm);
//securityService.autologin(accountForm.getUserName(), accountForm.getPassword());
return "redirect:login";
}
}
ok, 使用者傳送/registration get請求,我將註冊頁面返回
使用者填寫後 傳送/registration post請求,我在這裡要驗證(validation)使用者的填寫是否符合我的要求:
accountValidator.validate(accountForm, bindingResult);
來看看 accountValidator中的validate():
package git.com.postgraduate.bookstore.validator;
@Component
public class AccountValidator implements Validator {
@Autowired
AccountService accountService;
public boolean supports(Class<?> aClass) {
return Account.class.equals(aClass);
}
public void validate(Object o, Errors errors) {
Account account = (Account) o;
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName", "NotEmpty.accountForm.name");
if(account.getUserName().length() < 6 || account.getUserName().length() > 32) {
errors.rejectValue("userName", "Size.accountForm.username");
}
if(accountService.findByUsername(account.getUserName()) != null) {
errors.rejectValue("userName", "Duplicate.accountForm.username");
}
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "NotEmpty.accountForm.password");
if(account.getPassword().length() < 8 || account.getPassword().length() > 32) {
errors.rejectValue("password", "Size.accountForm.password");
}
if(!account.getPasswordConfirm().equals(account.getPassword())) {
errors.rejectValue("password", "Diff.accountForm.passwordConfirm");
}
}
}
validate(object o, errors errors) { } 傳入兩個引數,一個是要驗證的物件,物件所有屬性驗證假如有error便會將錯誤資訊(哪個field有什麼樣的錯誤)返回給errors物件,在controller裡就是bindingResult了,判斷bindingResult.hasErrors(),有則返回註冊頁面並顯示錯誤,無則註冊資訊,並重定向到登入頁面。這裡涉及到前端頁面如何展示錯誤資訊,不再贅述,可以看github中的頁面
ok ,這塊還是分開寫吧,下一次寫一下購物車功能的實現,做過的東西記性不好的話 要拿來經常總結的,下次假如用到了,可以用這個臨時大腦幫忙回憶一下。