1. 登录接口
用户登录模块的思路,先校验验证码是否匹配,然后校验用户名,密码是否匹配,匹配则用StpUtil生成token并返回。
import cn. dev33. satoken. stp. SaTokenInfo ;
import cn. dev33. satoken. stp. StpUtil ;
import cn. hutool. core. util. ObjectUtil ;
import cn. hutool. crypto. SecureUtil ;
import com. github. xiaoymin. knife4j. annotations. ApiOperationSupport ;
import com. github. xiaoymin. knife4j. annotations. ApiSort ;
import dev. samstevens. totp. exceptions. QrGenerationException ;
import im. gy. zfile. core. util. AjaxJson ;
import im. gy. zfile. module. config. model. dto. SystemConfigDTO ;
import im. gy. zfile. module. config. service. SystemConfigService ;
import im. gy. zfile. module. login. model. enums. LoginVerifyModeEnum ;
import im. gy. zfile. module. login. model. request. VerifyLoginTwoFactorAuthenticatorRequest ;
import im. gy. zfile. module. login. model. result. LoginTwoFactorAuthenticatorResult ;
import im. gy. zfile. module. login. model. result. LoginVerifyImgResult ;
import im. gy. zfile. module. login. request. UserLoginRequest ;
import im. gy. zfile. module. login. service. ImgVerifyCodeService ;
import im. gy. zfile. module. login. service. TwoFactorAuthenticatorVerifyService ;
import io. swagger. annotations. Api ;
import io. swagger. annotations. ApiOperation ;
import jakarta. annotation. Resource ;
import org. springframework. web. bind. annotation. * ; import javax. validation. Valid ;
@Api ( tags = "登录模块" )
@ApiSort ( 1 )
@RestController
@RequestMapping ( "/admin" )
public class LoginController { @Resource private SystemConfigService systemConfigService; @Resource private ImgVerifyCodeService imgVerifyCodeService; @Resource private TwoFactorAuthenticatorVerifyService twoFactorAuthenticatorVerifyService; @ApiOperationSupport ( order = 1 , ignoreParameters = { "zfile-token" } ) @ApiOperation ( "登录" ) @PostMapping ( "/login" ) public AjaxJson < ? > doLogin ( @Valid @RequestBody UserLoginRequest userLoginRequest) { String verifyCode = userLoginRequest. getVerifyCode ( ) ; String verifyCodeUUID = userLoginRequest. getVerifyCodeUUID ( ) ; SystemConfigDTO systemConfig = systemConfigService. getSystemConfig ( ) ; LoginVerifyModeEnum loginVerifyMode = systemConfig. getLoginVerifyMode ( ) ; String loginVerifySecret = systemConfig. getLoginVerifySecret ( ) ; if ( ObjectUtil . equals ( loginVerifyMode, LoginVerifyModeEnum . TWO_FACTOR_AUTHENTICATION_MODE ) ) { twoFactorAuthenticatorVerifyService. checkCode ( loginVerifySecret, verifyCode) ; } else if ( ObjectUtil . equals ( loginVerifyMode, LoginVerifyModeEnum . IMG_VERIFY_MODE ) ) { imgVerifyCodeService. checkCaptcha ( verifyCodeUUID, verifyCode) ; } if ( ObjectUtil . equals ( systemConfig. getUsername ( ) , userLoginRequest. getUsername ( ) ) && ObjectUtil . equals ( systemConfig. getPassword ( ) , SecureUtil . md5 ( userLoginRequest. getPassword ( ) ) ) ) { StpUtil . login ( "admin" ) ; SaTokenInfo tokenInfo = StpUtil . getTokenInfo ( ) ; return AjaxJson . getSuccess ( "登录成功" , tokenInfo) ; } return AjaxJson . getError ( "登录失败,账号或密码错误" ) ; } @ApiOperationSupport ( order = 2 ) @ApiOperation ( value = "注销" ) @PostMapping ( "/logout" ) public AjaxJson < ? > logout ( ) { StpUtil . logout ( ) ; return AjaxJson . getSuccess ( "注销成功" ) ; } @ApiOperationSupport ( order = 3 ) @ApiOperation ( value = "生成 2FA" ) @GetMapping ( "/2fa/setup" ) public AjaxJson < LoginTwoFactorAuthenticatorResult > setupDevice ( ) throws QrGenerationException { LoginTwoFactorAuthenticatorResult loginTwoFactorAuthenticatorResult = twoFactorAuthenticatorVerifyService. setupDevice ( ) ; return AjaxJson . getSuccessData ( loginTwoFactorAuthenticatorResult) ; } @ApiOperationSupport ( order = 4 ) @ApiOperation ( value = "2FA 验证并绑定" ) @PostMapping ( "/2fa/verify" ) public AjaxJson < ? > deviceVerify ( @Valid @RequestBody VerifyLoginTwoFactorAuthenticatorRequest verifyLoginTwoFactorAuthenticatorRequest) { twoFactorAuthenticatorVerifyService. deviceVerify ( verifyLoginTwoFactorAuthenticatorRequest) ; return AjaxJson . getSuccess ( ) ; } @ApiOperationSupport ( order = 5 ) @ApiOperation ( value = "获取登录验证方式" ) @GetMapping ( "/login/verify-mode" ) public AjaxJson < LoginVerifyModeEnum > loginVerifyMode ( ) { SystemConfigDTO systemConfig = systemConfigService. getSystemConfig ( ) ; return AjaxJson . getSuccessData ( systemConfig. getLoginVerifyMode ( ) ) ; } @ApiOperationSupport ( order = 6 ) @ApiOperation ( value = "获取图形验证码" ) @GetMapping ( "/login/captcha" ) public AjaxJson < LoginVerifyImgResult > captcha ( ) { LoginVerifyImgResult loginVerifyImgResult = imgVerifyCodeService. generatorCaptcha ( ) ; return AjaxJson . getSuccessData ( loginVerifyImgResult) ; } @ApiOperationSupport ( order = 7 ) @ApiOperation ( value = "检测是否已登录" ) @GetMapping ( "/login/check" ) public AjaxJson < Boolean > checkLogin ( ) { return AjaxJson . getSuccessData ( StpUtil . isLogin ( ) ) ; }
}
2. 图片验证码服务
图片验证码使用CaptchaUtil生成,并保存在缓存中,采用FIFO更新策略 验证方式是根据用户提供的uuid匹配对应的code,code与用户提供的相同则匹配成功。
import cn. hutool. cache. CacheUtil ;
import cn. hutool. cache. impl. FIFOCache ;
import cn. hutool. captcha. CaptchaUtil ;
import cn. hutool. captcha. CircleCaptcha ;
import cn. hutool. core. lang. UUID ;
import im. zhaojun. zfile. core. exception. LoginVerifyException ;
import im. zhaojun. zfile. module. login. model. result. LoginVerifyImgResult ;
import lombok. extern. slf4j. Slf4j ;
import org. springframework. stereotype. Service ; import java. util. Objects ;
@Service
@Slf4j
public class ImgVerifyCodeService { private final FIFOCache < String , String > verifyCodeCache = CacheUtil . newFIFOCache ( 100 , 60 * 1000L ) ; public LoginVerifyImgResult generatorCaptcha ( ) { CircleCaptcha captcha = CaptchaUtil . createCircleCaptcha ( 200 , 45 , 4 , 7 ) ; String code = captcha. getCode ( ) ; String imageBase64 = captcha. getImageBase64Data ( ) ; String uuid = UUID . fastUUID ( ) . toString ( ) ; verifyCodeCache. put ( uuid, code) ; LoginVerifyImgResult loginVerifyImgResult = new LoginVerifyImgResult ( ) ; loginVerifyImgResult. setImgBase64 ( imageBase64) ; loginVerifyImgResult. setUuid ( uuid) ; return loginVerifyImgResult; } public boolean verifyCaptcha ( String uuid, String code) { String expectedCode = verifyCodeCache. get ( uuid) ; return Objects . equals ( expectedCode, code) ; } public void checkCaptcha ( String uuid, String code) { boolean flag = verifyCaptcha ( uuid, code) ; if ( ! flag) { throw new LoginVerifyException ( "验证码错误或已失效." ) ; } } }
3. 双因素认证服务
验证使用verifier校验用户提供的secret和code
package im. zhaojun. zfile. module. login. service ; import dev. samstevens. totp. code. CodeVerifier ;
import dev. samstevens. totp. exceptions. QrGenerationException ;
import dev. samstevens. totp. qr. QrData ;
import dev. samstevens. totp. qr. QrDataFactory ;
import dev. samstevens. totp. qr. QrGenerator ;
import dev. samstevens. totp. secret. SecretGenerator ;
import im. zhaojun. zfile. core. exception. LoginVerifyException ;
import im. zhaojun. zfile. module. login. model. request. VerifyLoginTwoFactorAuthenticatorRequest ;
import im. zhaojun. zfile. module. login. model. result. LoginTwoFactorAuthenticatorResult ;
import im. zhaojun. zfile. module. config. model. dto. SystemConfigDTO ;
import im. zhaojun. zfile. module. login. model. enums. LoginVerifyModeEnum ;
import im. zhaojun. zfile. module. config. service. SystemConfigService ;
import org. springframework. stereotype. Service ; import javax. annotation. Resource ; import static dev. samstevens. totp. util. Utils . getDataUriForImage ;
@Service
public class TwoFactorAuthenticatorVerifyService { @Resource private SecretGenerator secretGenerator; @Resource private QrDataFactory qrDataFactory; @Resource private QrGenerator qrGenerator; @Resource private CodeVerifier verifier; @Resource private SystemConfigService systemConfigService; public LoginTwoFactorAuthenticatorResult setupDevice ( ) throws QrGenerationException { String secret = secretGenerator. generate ( ) ; QrData data = qrDataFactory. newBuilder ( ) . secret ( secret) . issuer ( "ZFile" ) . build ( ) ; String qrCodeImage = getDataUriForImage ( qrGenerator. generate ( data) , qrGenerator. getImageMimeType ( ) ) ; return new LoginTwoFactorAuthenticatorResult ( qrCodeImage, secret) ; } public void deviceVerify ( VerifyLoginTwoFactorAuthenticatorRequest verifyLoginTwoFactorAuthenticatorRequest) { String secret = verifyLoginTwoFactorAuthenticatorRequest. getSecret ( ) ; String code = verifyLoginTwoFactorAuthenticatorRequest. getCode ( ) ; if ( verifier. isValidCode ( secret, code) ) { SystemConfigDTO systemConfig = systemConfigService. getSystemConfig ( ) ; systemConfig. setLoginVerifyMode ( LoginVerifyModeEnum . TWO_FACTOR_AUTHENTICATION_MODE ) ; systemConfig. setLoginVerifySecret ( secret) ; systemConfigService. updateSystemConfig ( systemConfig) ; } else { throw new LoginVerifyException ( "验证码不正确" ) ; } } public void checkCode ( String loginVerifySecret, String verifyCode) { if ( ! verifier. isValidCode ( loginVerifySecret, verifyCode) ) { throw new LoginVerifyException ( "验证码错误或已失效" ) ; } } }