security第十四集 多因素认证-认证服务 📅 2026/6/19 15:16:13 多因素认证是一种实现安全访问控制的常见方法基本的设计理念在于用户如果想要访问最终的资源至少需要通过两种认证机制一种常见的做法是分成两个步骤实现第一步通过用户名和密码获取一个认证码Authentication Code第二步基于用户名和该认证码进行安全访问下面我们基于这种思想搭建一个多因素认证系统。可以借助上两集的用户体系上进行修改数据库中新建一个表auth_code , 其对应的实体类如下所示packagecom.kai.oauth.securityservice.entity;importlombok.Data;DatapublicclassAuthCode{privateStringid;privateStringusername;privateStringcode;}新建AuthController并暴露两个端点 /user/auth 和 /authcode/checkpackagecom.kai.oauth.securityservice.controller;importcom.kai.oauth.securityservice.entity.AuthCode;importcom.kai.oauth.securityservice.entity.User;importcom.kai.oauth.securityservice.service.UserService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.web.bind.annotation.PostMapping;importorg.springframework.web.bind.annotation.RequestBody;importorg.springframework.web.bind.annotation.RestController;importjavax.servlet.http.HttpServletResponse;importjava.util.HashMap;importjava.util.Map;RestControllerpublicclassAuthController{AutowiredprivateUserServiceuserService;//基于用户名和密码的认证返回authcodePostMapping(/user/auth)publicMapString,StringuserAuth(RequestBodyUseruser){StringauthcodeuserService.userAuth(user);MapString,StringmapnewHashMap();map.put(authcode,authcode);returnmap;}//基于用户名和authcode的认证PostMapping(/authcode/check)publicvoidcodeAuth(RequestBodyAuthCodeauthCode,HttpServletResponseresponse){if(userService.codeAuth(authCode)){response.setStatus(HttpServletResponse.SC_OK);}else{response.setStatus(HttpServletResponse.SC_FORBIDDEN);}}}上诉代码中的 userService.userAuth(user) 与 userService.codeAuth(authCode) 方法具体实现如下所示packagecom.kai.oauth.securityservice.service;importcom.kai.oauth.securityservice.entity.AuthCode;importcom.kai.oauth.securityservice.entity.User;importcom.kai.oauth.securityservice.mapper.AuthCodeMapper;importcom.kai.oauth.securityservice.mapper.UserMapper;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.security.authentication.BadCredentialsException;importorg.springframework.security.crypto.password.PasswordEncoder;importorg.springframework.stereotype.Service;importjava.util.UUID;ServicepublicclassUserService{AutowiredprivateUserMapperuserMapper;AutowiredprivateAuthCodeMapperauthCodeMapper;AutowiredprivatePasswordEncoderpasswordEncoder;//基于用户名与密码的认证实现publicStringuserAuth(Useruser){//根据username查询用户信息UseruuserMapper.selectByUserName(user.getUsername());if(u!null){//验证密码是否匹配if(passwordEncoder.matches(user.getPassword(),u.getPassword())){//密码匹配后生成authcode并返回StringauthCodegenerateOrRenewAuthCode(u);returnauthCode;}else{thrownewBadCredentialsException(Bad credentials);}}else{thrownewBadCredentialsException(Bad credentials);}}//基于用户名与authcode的认证实现publicbooleancodeAuth(AuthCodeauthCode){//通过username查询auth_code表数据AuthCodecodeauthCodeMapper.findAuthCodeByUsername(authCode.getUsername());if(code!null){//判断code与auth_code表的code是否一致if(authCode.getCode().equals(code.getCode())){returntrue;}}returnfalse;}//生成authcode方法privateStringgenerateOrRenewAuthCode(Useruser){//随机生成一个uuid当做authcodeStringgeneratedCodeUUID.randomUUID().toString().replaceAll(-,);//根据username查询auth_code表数据AuthCodeauthCodeauthCodeMapper.findAuthCodeByUsername(user.getUsername());//没有数据就新增一条有则更新if(authCode!null){authCode.setCode(generatedCode);authCodeMapper.updateAuthCode(authCode);}else{AuthCodecodenewAuthCode();code.setId(UUID.randomUUID().toString().replaceAll(-,));code.setUsername(user.getUsername());code.setCode(generatedCode);authCodeMapper.insertAuthCode(code);}returngeneratedCode;}}关于AuthCode的查询代码如下所示packagecom.kai.oauth.securityservice.mapper;importcom.kai.oauth.securityservice.entity.AuthCode;importorg.apache.ibatis.annotations.Mapper;MapperpublicinterfaceAuthCodeMapper{AuthCodefindAuthCodeByUsername(Stringusername);voidinsertAuthCode(AuthCodeauthCode);voidupdateAuthCode(AuthCodeauthCode);}AuthCodeMapper.xml 如下所示?xml version1.0encodingUTF-8?!DOCTYPEmapperPUBLIC-//mybatis.org//DTD Mapper 3.0//ENhttp://mybatis.org/dtd/mybatis-3-mapper.dtdmapper namespacecom.kai.oauth.securityservice.mapper.AuthCodeMapperresultMap idauthCodeResultMaptypecom.kai.oauth.securityservice.entity.AuthCodeid propertyidcolumnid/result propertyusernamecolumnusername/result propertycodecolumncode//resultMapselect idfindAuthCodeByUsernameresultMapauthCodeResultMapselect*from auth_code where username#{username}/selectinsert idinsertAuthCodeparameterTypecom.kai.oauth.securityservice.entity.AuthCodeinsert into auth_codevalues(#{id},#{username},#{code})/insertupdate idupdateAuthCodeparameterTypecom.kai.oauth.securityservice.entity.AuthCodeUPDATEauth_codeSETcode#{code}WHEREusername#{username}/update/mapper修改MyWebSecurityConfigAdapter把/user/auth 和 /authcode/check 端点设置成无需认证protectedvoidconfigure(HttpSecurityhttp)throwsException{http.csrf().disable().httpBasic();//任何请求都无需认证http.authorizeRequests().anyRequest().permitAll();}现在用postman来测试一下这两个端点先来试试访问/user/auth端点生成authcode利用生成的authcode值 来访问/authcode/check端点可以看到返回的status状态码是200而非403说明走的是 response.setStatus(HttpServletResponse.SC_OK);多因素认证的服务已经搭建完毕下一集 我们 新创建一个项目来请求此认证服务