认证服务
环境搭建
发送手机验证码
<script type="text/javascript" charset="utf-8">
$(function () {
$("#sendCode").click(function () {
// 1.给指定手机发送验证码
// 2.倒计时 60s
if ($(this).hasClass("disabled")) {
} else {
timeoutChangeStyle();
}
});
})
var num = 10;
function timeoutChangeStyle() {
$("#sendCode").attr("class", "disabled")
var str = num + "s后再次发送"
if (num === 0) {
str = "发送验证码";
num = 10;
$("#sendCode").attr("class", "");
} else {
// 1.给指定手机发送验证码
$.get("/sms/sendSms?mobile="+$("#mobile").val())
// 2.倒计时 60s
timeoutChangeStyle();
}
$("#sendCode").text(str);
num--
}
</script>
<a id="sendCode">发送验证码</a>
@RestController
public class LoginController {
@Autowired
private SmsFeignService smsFeignService;
@Autowired
private StringRedisTemplate redisTemplate;
@GetMapping("/sms/sendSms")
public R sendSms(@RequestParam("mobile") String mobile) {
// 1.接口防刷
String s = redisTemplate.opsForValue().get(AutoConstant.SMS_CODE_CACHE_PREFIX + mobile);
// 60s内不能在次发送
if (s != null && System.currentTimeMillis() - Long.parseLong(s.split("_")[1]) < 60000) {
return R.error();
}
// 生成验证码
String code = UUID.randomUUID().toString().substring(0, 5) + "_" + System.currentTimeMillis();
// 2.验证码再次校验 sms:code:13789983260,12345 10min有效期
redisTemplate.opsForValue().set(AutoConstant.SMS_CODE_CACHE_PREFIX + mobile, code, 10, TimeUnit.MINUTES);
return smsFeignService.sendSms(mobile, code.substring(0, 5));
}
}
@RestController
@RequestMapping("/sms")
public class SmsController {
@Autowired
private Sms sms;
/**
* 发送短信验证码
*
* @param mobile 移动
* @param code 代码
* @return {@link R}
*/
@GetMapping("/sendSms")
public R sendSms(@RequestParam("mobile") String mobile,@RequestParam("code") String code) {
sms.sendSms(mobile, code);
return R.ok();
}
}
@Component
public class Sms {
public void sendSms(String mobile, String code) {
String host = "http://dingxin.market.alicloudapi.com";
String path = "/dx/sendSms";
String method = "POST";
String appcode = "25429856ccf4401eb7260bceb3fa65d9";
Map<String, String> headers = new HashMap<String, String>();
//最后在header中的格式(中间是英文空格)为Authorization:APPCODE 83359fd73fe94948385f570e3c139105
headers.put("Authorization", "APPCODE " + appcode);
Map<String, String> querys = new HashMap<String, String>();
querys.put("mobile", mobile);
querys.put("param", "code:" + code);
querys.put("tpl_id", "TP1711063");
Map<String, String> bodys = new HashMap<String, String>();
try {
HttpResponse response = HttpUtils.doPost(host, path, method, headers, querys, bodys);
System.out.println(response.toString());
//获取response的body
//System.out.println(EntityUtils.toString(response.getEntity()));
} catch (Exception e) {
e.printStackTrace();
}
}
}
请求和页面的相互映射
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
/**
* 去登录页面
*
* @GetMapping("/login.html")
* public String loginHtml () {
* return "login";
*}
*/
registry.addViewController("/login.html").setViewName("login");
registry.addViewController("/reg.html").setViewName("reg");
}
}
用户注册功能
认证服务注册接口
/**
* 注册用户
*
* @return {@link String}
*/
@PostMapping("/register")
public String register(@Valid UserRegisterVo vo, BindingResult result) {
String goUrl = "redirect:/login.html";
if (result.hasErrors()) {
goUrl = "redirect:http://localhost:7777/reg.html";
} else {
// 1.校验验证码
String code = vo.getCode();
String mobile = vo.getMobile();
String redisCode = redisTemplate.opsForValue().get(AutoConstant.SMS_CODE_CACHE_PREFIX + mobile);
if (StringUtils.isNoneBlank(redisCode) && code.equals(redisCode)) {
goUrl = "redirect:http://localhost:7777/reg.html";
}else {
// 2.验证码正确
redisTemplate.delete(AutoConstant.SMS_CODE_CACHE_PREFIX + mobile); // 删除验证码
// 3.调用用户服务保存用户信息
memberFeignService.register(vo);
}
}
// 重定向到登录页面
return goUrl;
}
远程调用用户服务注册用户
/**
* 注册
*
* @return {@link R}
*/
@PostMapping("/register")
public R register(@RequestBody UserRegisterVo vo) {
try {
memberService.register(vo);
} catch (Exception e) {
e.printStackTrace();
}
return R.ok();
}
@Override
public void register(UserRegisterVo vo) {
// 检查用户是否唯一
checkMobile(vo.getMobile());
checkUsername(vo.getUsername());
// 保存
MemberEntity memberEntity = MemberEntity.builder()
.username(vo.getUsername())
.password(vo.getPassword())
.mobile(vo.getMobile())
.levelId(1L)
.build();
save(memberEntity);
}
@Override
public boolean checkMobile(String mobile) {
getEnt(mobile, "mobile");
return true;
}
public void getEnt(String check, String field) {
MemberEntity one = getOne(new QueryWrapper<MemberEntity>().eq(field, check));
if (one != null) {
throw new UserInfoExistException(one);
}
}
public class UserInfoExistException extends RuntimeException {
public UserInfoExistException(MemberEntity member) {
super(check(member));
}
public static String check(MemberEntity member) {
String res = "";
if (member.getUsername() != null) {
res = "用户名已经存在";
} else if (member.getMobile() != null) {
res = "手机号码已经存在";
} else if (member.getEmail() != null) {
res = "邮箱已经存在";
}
return res;
}
}
@Data
public class UserRegisterVo {
@NotEmpty(message = "用户名不能为空")
@Length(min = 6,max = 18,message = "用户名必须在6-18位之间")
private String username;
@NotEmpty(message = "密码不能为空")
@Length(min = 6,max = 18,message = "密码必须在6-18位之间")
private String password;
@NotEmpty(message = "手机号不能为空")
@Pattern(regexp = "^[1][0-9]{10}$",message = "手机号格式不正确")
private String mobile;
@NotEmpty(message = "验证码不能为空")
private String code;
}
密码加密
功能测试
用户登录
@PostMapping("/login")
public String login(UserLoginVo vo) {
// 远程
R login = memberFeignService.login(vo);
if (login.getCode() != 0) {
return "redirect:/login.html";
}
return "redirect:http://localhost:12000";
}
@PostMapping("/login")
public R login(@RequestBody UserLoginVo vo) {
MemberEntity member = memberService.login(vo);
if (member == null) {
return R.error("登录失败");
}
return R.ok();
}
@Override
public MemberEntity login(UserLoginVo vo) {
String loginName = vo.getLoginName();
String rawPasswd = vo.getPassword();
MemberEntity dbMember = getOne(new QueryWrapper<MemberEntity>()
.eq("username", loginName)
.or()
.eq("mobile", loginName)
.or()
.eq("email", loginName)
);
if (dbMember != null) {
// 密码匹配
String encryptedPassword = dbMember.getPassword();
if (new BCryptPasswordEncoder().matches(rawPasswd, encryptedPassword)) {
return dbMember;
}
}
return null;
}
社交登录
OAuth2.0
todo
记录登录状态-分布式session
统一存储
<dependency>
<groupId>org.springframework.session</groupId>
<artifactId>spring-session-data-redis</artifactId>
</dependency>
修改问题
自定义session配置
@Configuration
public class SessionConfig {
@Bean
public RedisSerializer<Object>redisSerializer() {
return new GenericJackson2JsonRedisSerializer();
}
@Bean
public CookieSerializer cookieSerializer() {
DefaultCookieSerializer cookie = new DefaultCookieSerializer();
cookie.setDomainName("localhost"); // cookie作用域,由于我没有搭建域名服务器,所以localhost就可以了
return cookie;
}
}
原理
@Override
public HttpSessionWrapper getSession(boolean create) {
HttpSessionWrapper currentSession = getCurrentSession();
if (currentSession != null) {
return currentSession;
}
S requestedSession = getRequestedSession();
if (requestedSession != null) {
if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
requestedSession.setLastAccessedTime(Instant.now());
this.requestedSessionIdValid = true;
currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
currentSession.markNotNew();
setCurrentSession(currentSession);
return currentSession;
}
}
else {
// This is an invalid session id. No need to ask again if
// request.getSession is invoked for the duration of this request
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"No session found by id: Caching result for getSession(false) for this HttpServletRequest.");
}
setAttribute(INVALID_SESSION_ID_ATTR, "true");
}
if (!create) {
return null;
}
if (SESSION_LOGGER.isDebugEnabled()) {
SESSION_LOGGER.debug(
"A new session was created. To help you troubleshoot where the session was created we provided a StackTrace (this is not an error). You can prevent this from appearing by disabling DEBUG logging for "
+ SESSION_LOGGER_NAME,
new RuntimeException("For debugging purposes only (not an error)"));
}
S session = SessionRepositoryFilter.this.sessionRepository.createSession();
session.setLastAccessedTime(Instant.now());
currentSession = new HttpSessionWrapper(session, getServletContext());
setCurrentSession(currentSession);
return currentSession;
}
单点登录
- 一处登录,处处登录。
- 统一session的名字