第二章登錄模塊
目錄
1.數據庫設計
設計用戶表
CREATE TABLE `miaosha_user`(
`id` bigint(20) NOT NULL COMMENT '用戶ID,手機號碼',
`nickname` varchar(255) NOT NULL,
`password` varchar(32) DEFAULT NULL COMMENT 'MD5 (MDS (pass明文+固定salt) + salt)',
`salt` varchar(10) DEFAULT NULL,
`head` varchar(128) DEFAULT NULL COMMENT '頭像,雲存儲的ID',
`register_date` datetime DEFAULT NULL COMMENT '註冊時間',
`last_login_date` datetime DEFAULT NULL COMMENT '上蔟登錄時間',
`login_count` int(11) DEFAULT '0' COMMENT '登錄次數',
PRIMARY KEY ( `id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
2.兩次MD5加密
原理
1.用戶端: PASS = MD5 (明文+固定Salt )
2.服務端: PASS = MD5 (用戶輸入+隨機Salt )
操作
1.引入依賴
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.6</version>
</dependency>
commons-codec:是Apache開源組織提供的用於摘要運算、編碼解碼的包。常見的編碼解碼工具Base64、MD5、Hex、SHA1、DES等。
commons-lang3:我們主要使用字符串操作類StringUtils
2.編寫MD5Util類
import org.apache.commons.codec.digest.DigestUtils;
public class MD5Util {
public static String md5(String src) {
return DigestUtils.md5Hex(src);
}
public static String formPassToDBPass(String formPass, String salt) {
String str = ""+salt.charAt(0)+salt.charAt(2) + formPass +salt.charAt(5) + salt.charAt(4);
return md5(str);
}
}
salt作用:對密碼進行拼接增加安全性
前端的MD5加密
var g_passsword_salt="1a2b3c4d"
var inputPass = $("#password").val();
var salt = g_passsword_salt;
var str = ""+salt.charAt(0)+salt.charAt(2) + inputPass +salt.charAt(5) + salt.charAt(4);
var password = md5(str);
3. JSR303參數檢驗
普通參數校驗
我們先看一下原來的參數校驗代碼
@RequestMapping("/do_login")//異步
@ResponseBody
public Result<Boolean> doLogin( HttpServletResponse response, LoginVo loginVo, HttpSession session) throws IOException {
//參數校驗
String password = loginVo.getPassword();
String mobile = loginVo.getMobile();
if (StringUtils.isEmpty(password)){
return Result.error(CodeMsg.PASSWORD_EMPTY);
}if (StringUtils.isEmpty(mobile)){
return Result.error(CodeMsg.MOBILE_EMPTY);
}if (!ValidatorUtil.isMobile(mobile)){
return Result.error(CodeMsg.MOBILE_ERROR);
}
//登錄
......
}
通過正則表達式Matcher和Pattern來判斷手機號的格式
public class ValidatorUtil {
private static final Pattern mobile_pattern=Pattern.compile("1\\d{10}");
public static boolean isMobile(String src){
if (StringUtils.isEmpty(src)){
return false;
}
Matcher m=mobile_pattern.matcher(src);
return m.matches();
}
}
通過LoginVo來接收前端傳入的參數,並且進行參數校驗
JSR303參數檢驗
1.自定義註解
使用的關鍵字@interface
來自定義註解。在底層實現上,所有定義的註解都會自動繼承java.lang.annotation.Annotation接口。
元註解:專門修飾註解的註解
註解裏面定義的是:註解類型元素!
該自定義註解類中用到了四種元註解,最後一個@Constraint指定了校驗類,也就是接下來的IsMobileValidator類。值得一提的是除了自定義的message、require屬性外,下面的groups和payload也是必須添加的。
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = {IsMobileValidator.class})
public @interface IsMobile {
boolean required() default true;
String message() default "手機號碼格式有誤";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
2.校驗類
需要實現ConstraintValidator接口。 接口使用了泛型,需要指定兩個參數,第一個自定義註解類,第二個爲需要校驗的數據類型。 實現接口後要override兩個方法,分別爲initialize方法和isValid方法。其中initialize爲初始化方法,可以在裏面做一些初始化操作,isValid方法就是我們最終需要的校驗方法了。可以在該方法中實現具體的校驗步驟。本示例中進行了簡單的手機號校驗。 完成這幾部分之後,一個簡單的自定義校驗註解就完成啦,不要忘記在使用的時候加上@Valid註解開啓valid校驗。 那麼如何獲取在註解中定義的message信息呢? 在valid校驗中,如果校驗不通過,會產生BindException異常,捕捉到異常後可以獲取到defaultMessage也就是自定義註解中定義的內容,具體實現如下:
import org.apache.commons.lang3.StringUtils;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class IsMobileValidator implements ConstraintValidator<IsMobile, String> {
private boolean required=false;
@Override
public void initialize(IsMobile constraintAnnotation) {
required=constraintAnnotation.required();
}
@Override
public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
if (required){
return ValidatorUtil.isMobile(s);
}else {
if (StringUtils.isEmpty(s))
return true;
else
return ValidatorUtil.isMobile(s);
}
}
}
把我們的註解加上去,並且使用@isValid開啓valid校驗
public class LoginVo {
@NotNull
@IsMobile
private String mobile;
@NotNull
@Length(min = 32)
private String password;
//set和get方法
}
@RequestMapping("/do_login")//異步
@ResponseBody
public Result<Boolean> doLogin( HttpServletResponse response, @isValid LoginVo loginVo, HttpSession session) throws IOException {
//使用了自定義註解並開啓@isValid
//登錄
......
}
4.全局異常處理器
@ControllerAdvice :這是一個非常有用的註解,顧名思義,這是一個增強的 Controller。使用這個 Controller ,可以實現三個方面的功能:
- 全局異常處理
- 全局數據綁定
- 全局數據預處理
import java.util.List;
/**
* 全局異常處理器
*/
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {
@ExceptionHandler(value = Exception.class)
public Result<String> exceptionHandler(HttpServletRequest request,Exception e){
e.printStackTrace();
if (e instanceof GlobalException){
GlobalException ex=(GlobalException)e;
return Result.error(ex.getCm());
}else if (e instanceof BindException){
BindException ex=(BindException)e;
List<ObjectError> errors = ex.getAllErrors();
ObjectError error=errors.get(0);
String msg =error.getDefaultMessage();
return Result.error(CodeMsg.BIND_ERROR.fillArgs(msg));
}else {
return Result.error(CodeMsg.SERVER_ERROR);
}
}
}
5.分佈式Session
1.在登錄完成後生成token寫進cookie並存到Session中,並且存到redis中<token,user>的形式
2.當在其他頁面時,token隨之傳遞,編寫getByToken來得到我們存到Redis的user並且從新定義一個cookie,來更新cookie時效
//生成cookie
private void addCookie(HttpServletResponse response,String token,MiaoshaUser user){
//System.out.println("生成cookie");
redisService.set(MiaoshaUserKey.token,token,user);
Cookie cookie=new Cookie(COOKI_NAME_TOKEN,token);
cookie.setMaxAge(MiaoshaUserKey.token.expireSeconds());
cookie.setPath("/");
response.addCookie(cookie);
}
public MiaoshaUser getByToken(HttpServletResponse response,String token) {
if (StringUtils.isEmpty(token))
return null;
MiaoshaUser user= redisService.get(MiaoshaUserKey.token,token,MiaoshaUser.class);
//延長有效期
//生成cookie
if (user!=null){
addCookie(response,token,user);
}
return user;
}
3.重寫WebMvcConfigurerAdapter中的addArgumentResolvers是的controller可以識別user信息
未使用前
@RequestMapping("/to_list")
public String list(HttpServletResponse response, Model model,
@CookieValue(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String cookieToken,
@RequestParam(value = MiaoshaUserService.COOKI_NAME_TOKEN,required = false) String paramToken){
if (StringUtils.isEmpty(cookieToken)&&StringUtils.isEmpty(paramToken))
return "login";
String token=StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
MiaoshaUser user=userService.getByToken(response,token);
model.addAttribute("user",user);
return "goods_list";
}
使用後
@RequestMapping("/to_list")
public String list( Model model,MiaoshaUser user){
System.out.println(user);
model.addAttribute("user",user);
return "goods_list";
}
@Configuration
public class WebConfig extends WebMvcConfigurerAdapter {
@Autowired
UserArgumentResolver userArgumentResolver;
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(userArgumentResolver);
}
}
@Service
public class UserArgumentResolver implements HandlerMethodArgumentResolver {
@Autowired
MiaoshaUserService userService;
@Override
public boolean supportsParameter(MethodParameter methodParameter) {
Class<?> clazz=methodParameter.getParameterType();
return clazz== MiaoshaUser.class;
}
@Override
public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
HttpServletRequest request=nativeWebRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse response=nativeWebRequest.getNativeResponse(HttpServletResponse.class);
String paramToken=request.getParameter(MiaoshaUserService.COOKI_NAME_TOKEN);
String cookieToken=getCookieValue(request,MiaoshaUserService.COOKI_NAME_TOKEN);
if (StringUtils.isEmpty(cookieToken)&&StringUtils.isEmpty(paramToken))
return null;
String token=StringUtils.isEmpty(paramToken)?cookieToken:paramToken;
System.out.println(token);
MiaoshaUser user=userService.getByToken(response,token);
return user;
}
private String getCookieValue(HttpServletRequest request, String cookiName) {
Cookie[] cookies = request.getCookies();
for (Cookie cookie: cookies) {
if (cookie.getName().equals(cookiName))
return cookie.getValue();
}
return null;
}
}
第二章就結束了