主要分为3大部分
1.配置阿里云短信业务
2.uniapp手机登录模块设计以及信息提交
3.后端接收手机登录信息,反馈登录结果
项目地址:https://gitee.com/quxianyyy/sms-project
一步可以直接参考博主Axn_很优秀的文章,申请获取到key和secret
第二步在uniapp设计一个手机登录的页面(ui框架是color-ui)
<form @submit="login">
<view class="cu-form-group">
<view class="title">手机号码</view>
<input placeholder="请输入手机号码" name="phoneNumbers" @input="change" maxlength="11" type="number"></input>
<view class="cu-capsule radius">
<view class='cu-tag bg-blue '>
+86
</view>
<view class="cu-tag line-blue">
中国大陆
</view>
</view>
</view>
<view class="cu-form-group">
<view class="title">验证码</view>
<input placeholder="请输入验证码" name="code" type="number" maxlength="4"></input>
<button class='cu-btn bg-green shadow' @tap="countDown" :disabled="this.countNum<60 && this.countNum>0?true:false">
<!-- <span>{{this.$store.state.countNum<60 && this.$store.state.countNum>0?'倒计时'+this.$store.state.countNum:'验证码'}}</span> -->
<span>{{this.countNum<60 && this.countNum>0?this.countNum+'s':'验证码'}}</span>
</button>
</view>
<view>
<button form-type="submit">登录</button>
</view>
</form>
将接收的信息上传到后端验证
this.$http.post('/authentication/mobile', {}, {
params: {
mobile: params.phoneNumbers,
code: params.code
}
})
详细参考项目地址里面的源码
第三部分为2种情况.
1.发送手机验证码的api
controller代码
@GetMapping("/send/{phoneNumbers}")
public String code(@PathVariable("phoneNumbers") String phoneNumbers){
//调用发送方法
//如果验证码存在 存在就是非过期
String code=(String)redisTemplate.opsForValue().get(phoneNumbers);
if (!StringUtils.isEmpty(code)){
return phoneNumbers+":"+code+"已经存在,还没过期";
}
//如果不存在
code=String.valueOf(new Random().nextLong()).substring(1,5);
HashMap<String, Object> param=new HashMap<>();
param.put("code",code);
//如果发送成功,存手机验证码
boolean isSend=sendSms.send(phoneNumbers,"SMS_189712947",param);
if (isSend){
redisTemplate.opsForValue().set(phoneNumbers,code,500, TimeUnit.MINUTES);
return phoneNumbers+":"+code+"发送成功!";
}else {
return "发送失败";
}
}
service
public boolean send(String phoneNum, String templateCode, Map<String, Object> code) {
DefaultProfile profile = DefaultProfile.getProfile("cn-hangzhou", "你的阿里云短信key", "密码");
IAcsClient client = new DefaultAcsClient(profile);
CommonRequest request = new CommonRequest();
request.setMethod(MethodType.POST);
request.setDomain("dysmsapi.aliyuncs.com");//不要改
request.setVersion("2017-05-25");//不要改
request.setAction("SendSms");
//自定义的参数(手机号,验证码,签名,模版)
request.putQueryParameter("PhoneNumbers", phoneNum);//手机号码
request.putQueryParameter("SignName","你的签名名字");
request.putQueryParameter("TemplateCode",templateCode);//模版代码
//构建短信验证码
// HashMap hashMap=new HashMap();
// hashMap.put("code",2233);//code里面的就是你发送给用户手机的验证码
request.putQueryParameter("TemplateParam", JSONObject.toJSONString(code));
try {
CommonResponse response = client.getCommonResponse(request);
System.out.println(response.getData());
return response.getHttpResponse().isSuccess();
} catch (ServerException e) {
e.printStackTrace();
} catch (ClientException e) {
e.printStackTrace();
}
return false;
}
2.处理上传的手机相关信息
如果是普通的boot项目,没有权限之类的控制.单独起一个接口进行参数接收,从redis根据手机号拿出验证码进行对比验证即可
如果是基于springsecuity搭建起的boot项目,因为默认只接收用户名,不能满足需求,可以通过自定义过滤器实现(完整代码参考项目地址里面)
//1.1写一个拦截器
public class SmsCodeAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
protected SmsCodeAuthenticationFilter(String defaultFilterProcessesUrl) {
//验证成功后
super(defaultFilterProcessesUrl);
}
protected SmsCodeAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
}
//判断手机和验证码是否正常
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
System.out.println(request.getParameter("mobile"));
System.out.println(request.getParameter("code"));
System.out.println("1.--------请求手机登录------判断手机和验证码是否正常");
//如果方法的类型不是POST,拒绝处理
if (!request.getMethod().equals("POST")) {
System.out.println("方法不是POST,手机登录结束");
throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());
}
RedisTemplate redisTemplate=
(RedisTemplate) SpringContextUtil.getBean("redisTemplate");
ObjectMapper objectMapper=SpringContextUtil.getBean(ObjectMapper.class);
String mobile = request.getParameter("mobile");
Object code=redisTemplate.opsForValue().get(mobile);
httpServletResponse.setCharacterEncoding("utf-8");
System.out.println("2.当前请求登录的手机号为"+mobile);
/**
* 情况:
* 1.存在 当前处于用手机验证码登录的状态
* 2.不存在 验证码过期或者还没发送验证码
*/
//验证码不存在
if (StringUtils.isEmpty(code)){
System.out.println("验证码过期或未发送");
CommonResult commonResult=new CommonResult(406,"验证码未发送或验证码已过期");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(commonResult));
return null;
}
//验证码存在
String pcode=request.getParameter("code");
System.out.println("3.判断验证码是否正确[前端传入的验证码"+pcode+"],[redis数据库存储的验证码"+code+"]");
if (!code.equals(pcode)) {
System.out.println("验证码错误");
CommonResult commonResult=new CommonResult(407,"验证码错误");
httpServletResponse.getWriter().write(objectMapper.writeValueAsString(commonResult));
return null;
}
System.out.println("4.验证码和手机都正常,颁发token和返回用户信息");
// 封装令牌
SmsCodeAuthenticationToken authRequest = new SmsCodeAuthenticationToken(mobile,pcode);
setDetails(request, authRequest);
// 开始认证
//调用SmsCodeAuthenticationProvider的authenticate
return this.getAuthenticationManager().authenticate(authRequest);
}
protected void setDetails(HttpServletRequest request, SmsCodeAuthenticationToken authRequest) {
authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
}
}
//1.2配置自定义的信息存储类
package com.example.authenticationserver.authentication;
import com.example.authenticationserver.pojo.MysqlUser;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import java.util.Collection;
/**
* @author qm
* @date 2020/5/15 13:10
*/
public class SmsCodeAuthenticationToken extends AbstractAuthenticationToken {
private static final long serialVersionUID = 1L;
/** 身份 */
private final Object principal;
/** 验证码 */
private String code;
/** 权限 */
private String role;
/** 别名 */
private String nickName;
/** 用户信息 */
private MysqlUser mysqlUser;
public String getRole() {
return role;
}
public void setRole(String role) {
this.role = role;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
public SmsCodeAuthenticationToken(
Collection<? extends GrantedAuthority> authorities,MysqlUser mysqlUser
,Object principal,String nickName) {
super(authorities);
this.principal = principal;
this.mysqlUser=mysqlUser;
this.nickName = nickName;
}
public void clear(){
this.mysqlUser=null;
}
public SmsCodeAuthenticationToken(Object mobile, String code) {
super(null);
this.code=code;
this.principal = mobile;
setAuthenticated(false);
}
public SmsCodeAuthenticationToken(Object principal, String code,Collection<? extends GrantedAuthority> authorities) {
super(authorities);
this.code=code;
this.principal = principal;
// must use super, as we override
super.setAuthenticated(true);
}
public SmsCodeAuthenticationToken(Object principal,Collection<? extends GrantedAuthority> authorities,MysqlUser mysqlUser) {
super(authorities);
this.principal = principal;
// must use super, as we override
super.setAuthenticated(true);
this.mysqlUser=mysqlUser;
}
public String getCode() {
return code;
}
public MysqlUser getMysqlUser() {
return mysqlUser;
}
@Override
public Object getPrincipal() {
return this.principal;
}
@Override
public void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException {
if (isAuthenticated) {
throw new IllegalArgumentException("Cannot set this token to trusted - use constructor which takes a GrantedAuthority list instead");
}
super.setAuthenticated(false);
}
@Override
public void eraseCredentials() {
super.eraseCredentials();
}
@Override
public Object getCredentials() {
return null;
}
}
//1.3书写过滤器配置信息
@Component
public class SmsCodeAuthenticationSecurityConfig
extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
@Qualifier("myAuthenticationSuccessHandler")
@Autowired
private AuthenticationSuccessHandler authenticationSuccessHandler;
@Override
public void configure(HttpSecurity http) throws Exception {
SmsCodeAuthenticationFilter smsCodeAuthenticationFilter =
new SmsCodeAuthenticationFilter("/authentication/mobile");
smsCodeAuthenticationFilter.setAuthenticationManager(http.getSharedObject(AuthenticationManager.class));
smsCodeAuthenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
SmsCodeAuthenticationProvider provider = new SmsCodeAuthenticationProvider();
// 将SmsCodeAuthenticationFilter放到过滤器链的UsernamePasswordAuthenticationFilter的后面
http
.authenticationProvider(provider)
.addFilterAfter(smsCodeAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
}
}
//1.4配置验证器,配置如何验证
//如何验证
public class SmsCodeAuthenticationProvider implements AuthenticationProvider {
@Autowired
private UserDetailsService userDetailsService;
/**
* 验证码和手机都没问题,开始用户操作
*
* @param authentication
* @return
* @throws AuthenticationException
*/
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
MysqlUserDao mysqlUserDao= SpringContextUtil.getBean(MysqlUserDao.class);
System.out.println("5.--------用户验证---判断该用户是否存在于数据库,不存在则创建");
String mobile=(String) authentication.getPrincipal();
MysqlUser user = mysqlUserDao.findOne(mobile);
if (user==null){
System.out.println("6.用户未注册,用户注册开始........");
MysqlUser mysqlUser = new MysqlUser();
mysqlUser.setGender(0);
mysqlUser.setNickName(mobile);
mysqlUser.setRole("user");
mysqlUser.setAccount(mobile);
mysqlUser.setMobile(mobile);
mysqlUser.setAvatarUrl("http://qzapp.qlogo.cn/qzapp/1104455702/80B727209F00D44C35166EB2D6B21086/30");
mysqlUser.setPassword(new BCryptPasswordEncoder().encode("123"));
int i = mysqlUserDao.add(mysqlUser);
System.out.println("注册结果"+i);
user = mysqlUserDao.findOne(mobile);
}else {
System.out.println("6.用户存在");
}
// //验证正常,封装用户信息返回
// Collection<GrantedAuthority> authorities=new ArrayList<>();
// authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
SmsCodeAuthenticationToken authenticationToken = (SmsCodeAuthenticationToken) authentication;
//设置权限
Collection<GrantedAuthority> authorities=new ArrayList<>();
authorities.add(new SimpleGrantedAuthority(user.getRole()));
//其他信息
SmsCodeAuthenticationToken authenticationResult =
new SmsCodeAuthenticationToken(user.getAccount(),authorities,user);
authenticationResult.setDetails(authenticationToken.getDetails());
return authenticationResult;
}
//模拟手机号进行验证
private void simulationVerification(String number, String pcode){
//当收到验证码8888和手机号13005630595才算成功
final String phone="13005630595";
final String code="8888";
if (!phone.equals(number)){
throw new InternalAuthenticationServiceException("手机号不存在");
}
if (!code.equals(pcode)) {
//判断验证码是否想等
throw new InternalAuthenticationServiceException("验证码错误");
}
}
//
@Override
public boolean supports(Class<?> authentication) {
return SmsCodeAuthenticationToken.class.isAssignableFrom(authentication);
}
}
//1.5到安全框架的配置里面进行配置更新
@Autowired
private SmsCodeAuthenticationSecurityConfig smsCodeAuthenticationSecurityConfig;
@Override
protected void configure(HttpSecurity http) throws Exception {
//引入短信拦截器配置
http
.apply(smsCodeAuthenticationSecurityConfig);
//1.6编写登录成功,如何处理(生成token和获取用户信息进行返回)
@Slf4j
@Component
public class MyAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Autowired
private ClientDetailsService clientDetailsService;
//
@Qualifier("defaultAuthorizationServerTokenServices")
@Autowired
private AuthorizationServerTokenServices authorizationServerTokenServices;
@Autowired
private ObjectMapper objectMapper;
@Autowired
private MysqlUserDao mysqlUserDao;
@Override
public void onAuthenticationSuccess(
HttpServletRequest request, HttpServletResponse response, Authentication authentication)
throws IOException, ServletException, IOException {
SmsCodeAuthenticationToken smsCodeAuthenticationToken=(SmsCodeAuthenticationToken)authentication;
MysqlUser user = ((SmsCodeAuthenticationToken) authentication).getMysqlUser();
smsCodeAuthenticationToken.clear();
System.out.println("7.登录完成,准备返回token和用户信息");
// Collection<GrantedAuthority> authorities=new ArrayList<>();
// //设置权限
// authorities.add(new SimpleGrantedAuthority("admin"));
// SmsCodeAuthenticationToken smsCodeAuthenticationToken1=
// new SmsCodeAuthenticationToken(authorities,"13005630595","ROLE_ADMIN","曲线");
// System.out.println(smsCodeAuthenticationToken.getCode());
// System.out.println(smsCodeAuthenticationToken.getPrincipal());
// System.out.println(smsCodeAuthenticationToken);
// System.out.println(authentication);
// System.out.println("7.准备生成token");
OAuth2AccessToken token = createToken(authentication);
System.out.println("7.1:user信息"+user);
System.out.println("7.2:token信息"+token);
//封装用户信息和token信息返回
Map map=new HashMap<>();
map.put("code",200);
map.put("authResult",token);
//map.put("userInfo",mysqlUserDao.findOne((String) authentication.getPrincipal()));
map.put("userInfo",user);
response.setContentType("application/json;charset=UTF-8");
response.getWriter().write(objectMapper.writeValueAsString(map));
}
public OAuth2AccessToken createToken(Authentication authentication){
ClientDetails clientDetails=clientDetailsService.loadClientByClientId("myapp_id");
TokenRequest tokenRequest = new TokenRequest(new HashMap<>(), "myapp_id",clientDetailsService.loadClientByClientId("myapp_id").getScope(), "custom");
OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(clientDetails);
OAuth2Authentication oAuth2Authentication = new OAuth2Authentication(oAuth2Request, authentication);
OAuth2AccessToken accessToken = authorizationServerTokenServices.createAccessToken(oAuth2Authentication);
return accessToken;
}
}