认证服务-----技术点及亮点

news2024/11/16 23:56:17

大技术

Nacos做注册中心

把新建的微服务注册到Nacos上去

两个步骤

  • 在配置文件中配置应用名称、nacos的发现注册ip地址,端口号
  • 在启动类上用@EnableDiscoveryClient注解开启注册功能

使用Redis存验证码信息

加入依赖配置地址和端口号即可

直接注入StringRedisTemplate模板类用就是

使用如下

//根据key获取redis中的值

String redisCode= redisTemplate.opsForValue().get(redis中的key值);

//存值到Redis中

redisTemplate.opsForValue().set(key值,value值,过期时间, 过期单位(TimeUnit.MINUTES));

使用gitee作社交登录(OAuth2.0)(亮点1)

这里由于微博开发者权限申请太慢了就使用gitee来实现社交登录

社交登录的步骤

(1)前端调用第三方应用作为社交登录

这个跟以往的模式不一样,以往是前段直接给后端发送请求,然后后端处理请求,这个是先调用第三方应用作为社交登录(也就是先跳转到gitee的登录授权页面),然后用户登录自己的gitee账号密码进行授权,授权成功后会跳转到指定的应用回调地址,然后在后端来处理这个应用回调地址的请求

gitee开发者后台管理链接:https://gitee.com/oauth/applications/16285

调用gitee第三方登录的url地址:<a href="https://gitee.com/oauth/authorize?client_id=自己应用的Client ID&redirect_uri=自己应用的成功回调地址&response_type=code&state=1">

<li>
  <a href="https://gitee.com/oauth/authorize?client_id=32459f971ce6d89cfb9f70899525455d0653cb804f16b38a304e3447dc97d673&redirect_uri=http://auth.saodaimall.com/callback&response_type=code&state=1">
    <img style="width: 50px;height: 18px;margin-top: 35px;" src="/static/login/JD_img/gitee.png"/>
  </a>
</li>

(2)社交服务OAuth2Controller来处理应用回调地址/callback请求

分流程:(其实就只有三行代码是要自己配置的(OAuth2Controller的gitee的42-44行),其他的基本上是固定的)

1>封装AccessTokenDTO对象然后发给码云服务器,如果AccessTokenDTO对象正确的话就返回一个access_token通行令牌(其实准确来说是用户授权后会返回一个code,然后把code和其他一些信息封装成AccessTokenDTO对象去找码云服务器获取到一个access_token通行令牌,最后通过这个access_token通行令牌去找码云服务器要这个用户在gitee上公开的资料信息)

2>获取到了access_token通行令牌去找码云服务器取该用户的公开信息并且转为通用gitee社交登录GiteeUser对象

3>远程调用会员服务来进行社交登录

package com.saodai.saodaimall.auth.controller;

import com.alibaba.fastjson.TypeReference;
import com.saodai.common.utils.R;
import com.saodai.common.vo.MemberResponseVo;
import com.saodai.saodaimall.auth.component.GitheeProvider;
import com.saodai.saodaimall.auth.feign.MemberFeignService;
import com.saodai.saodaimall.auth.vo.AccessTokenDTO;
import com.saodai.saodaimall.auth.vo.GiteeUser;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import javax.servlet.http.HttpSession;

import static com.saodai.common.constant.AuthServerConstant.LOGIN_USER;


/**
* 社交第三方授权登录
**/

@Slf4j
@Controller
public class OAuth2Controller {
    
    @Autowired
    private MemberFeignService memberFeignService;
    @Autowired
    private AccessTokenDTO accessTokenDTO;
    @Autowired
    private GitheeProvider githeeProvider;
    
    @GetMapping(value = "/callback")
    // /callback?code=e867a1f4575d4a6161e3249423a0403898253bc593e4b031a8771739ee6769f5&state=1
    public String gitee(@RequestParam(name = "code") String code,@RequestParam(name = "state") String state, HttpSession session) throws Exception {
        System.out.println(code);
        //下面三行代码都是自己应用的值,可以在gitee的第三方应用中看到对应的值
        accessTokenDTO.setClient_id("32459f971ce6d89cfb9f70899525455d0653cb804f16b38a304e3447dc97d673");
        accessTokenDTO.setClient_secret("f3046c911c03cadcded986062708150d4232af3ca6aef0259e5a0198d2c15ba5");
        accessTokenDTO.setRedirect_uri("http://auth.saodaimall.com/callback");
        accessTokenDTO.setCode(code);
        accessTokenDTO.setState(state);
        String accessToken = githeeProvider.getAccessToken(accessTokenDTO);
        //2、处理
        if (!StringUtils.isEmpty(accessToken)) {
            //获取到了access_token,转为通用gitee社交登录对象
            GiteeUser giteeUser = githeeProvider.getGiteeUser(accessToken);
            //知道了哪个社交用户
            //1)、当前用户如果是第一次进网站,自动注册进来(为当前社交用户生成一个会员信息,以后这个社交账号就对应指定的会员)
            //登录或者注册这个社交用户
            //调用远程服务
            R oauthLogin = memberFeignService.oauthLogin(giteeUser);
            if (oauthLogin.getCode() == 0) {
                MemberResponseVo memberResponseVo = oauthLogin.getData("data", new TypeReference<MemberResponseVo>() {});
                log.info("登录成功:用户信息:{}",memberResponseVo.toString());
                
                //1、第一次使用session,命令浏览器保存卡号,JSESSIONID这个cookie
                //以后浏览器访问哪个网站就会带上这个网站的cookie
                //TODO 1、默认发的令牌。当前域(解决子域session共享问题)
                //TODO 2、使用JSON的序列化方式来序列化对象到Redis中
                session.setAttribute(LOGIN_USER,memberResponseVo);
                
                //2、登录成功跳回首页
                return "redirect:http://saodaimall.com";
            } else {
                
                return "redirect:http://auth.saodaimall.com/login.html";
            }
            
        } else {
            return "redirect:http://auth.saodaimall.com/login.html";
        }
        
    }
    
}
package com.saodai.saodaimall.auth.vo;


/**
 * AccessTokenDTO对象封装(gitee社交登录令牌)
 */

import org.springframework.stereotype.Component;

@Component
public class AccessTokenDTO {
    private String client_id;
    private String client_secret;
    private String code;
    private String redirect_uri;
    private String state;

    public String getClient_id() {
        return client_id;
    }

    public void setClient_id(String client_id) {
        this.client_id = client_id;
    }

    public String getClient_secret() {
        return client_secret;
    }

    public void setClient_secret(String client_secret) {
        this.client_secret = client_secret;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getRedirect_uri() {
        return redirect_uri;
    }

    public void setRedirect_uri(String redirect_uri) {
        this.redirect_uri = redirect_uri;
    }

    public String getState() {
        return state;
    }

    public void setState(String state) {
        this.state = state;
    }
}
package com.saodai.saodaimall.auth.component;


import com.alibaba.fastjson.JSON;
import com.saodai.saodaimall.auth.vo.AccessTokenDTO;
import com.saodai.saodaimall.auth.vo.GiteeUser;
import okhttp3.*;
import org.springframework.stereotype.Component;

import java.io.IOException;

/**
 * 请求码云服务器
 */
@Component
public class GitheeProvider {

    //发起post请求获取AccessToken
    public String getAccessToken(AccessTokenDTO accessTokenDTO){
        MediaType mediaType= MediaType.get("application/json; charset=utf-8");
        OkHttpClient client = new OkHttpClient();
        RequestBody body = RequestBody.create(mediaType, JSON.toJSONString(accessTokenDTO));
        Request request = new Request.Builder()
                .url("https://gitee.com/oauth/token?grant_type=authorization_code&code="+accessTokenDTO.getCode()+
                        "&client_id="+accessTokenDTO.getClient_id()+"&redirect_uri="+accessTokenDTO.getRedirect_uri()+
                        "&client_secret="+accessTokenDTO.getClient_secret())
                .post(body)
                .build();
        try (Response response = client.newCall(request).execute()) {
            String string = response.body().string();
            System.out.println(string);
            String str1 = string.split(":")[1];
            String str2 = str1.split("\"")[1];
            return str2;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    //发起get请求返回GitUser对象,
    public GiteeUser getGiteeUser(String token){
        OkHttpClient client = new OkHttpClient();
        Request request = new Request.Builder()
                .url("https://gitee.com/api/v5/user?access_token="+token)
                .build();
        try (Response response = client.newCall(request).execute()) {
            String string=response.body().string();
            GiteeUser giteeUser = JSON.parseObject(string, GiteeUser.class);
            return giteeUser;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
}
package com.saodai.saodaimall.auth.vo;

import lombok.Data;

/**
 * GiteeUser对象封装(社交登录的gitee对象)
 */
@Data
public class GiteeUser {
    //gitee用户名称
    private String name;
    //gitee用户id
    private String id;
    //gitee用户自我介绍
    private String bio;


}

(3)远程调用会员服务来进行社交登录

/**
     * 社交登录
     * @param giteeUser
     * @return
     * @throws Exception
     */
  @PostMapping(value = "/oauth2/login")
    public R oauthLogin(@RequestBody GiteeUser giteeUser) throws Exception {

        MemberEntity memberEntity = memberService.login(giteeUser);

        if (memberEntity != null) {
            return R.ok().setData(memberEntity);
        } else {
            return R.error(BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getCode(),BizCodeEnum.LOGINACCT_PASSWORD_EXCEPTION.getMessage());
        }
    }
 /**
     * 社交登录
     * @param giteeUser
     * @return
     * @throws Exception
     */
    @Override
    public MemberEntity login(GiteeUser giteeUser) throws Exception {

        //获取gitee用户唯一id
        String giteeUserId = giteeUser.getId();

        //1、判断当前社交用户是否已经登录过系统
        MemberEntity memberEntity = this.baseMapper.selectOne(new QueryWrapper<MemberEntity>().eq("social_id", giteeUserId));
        //这个用户已经注册过
        if (memberEntity != null) {
            return memberEntity;
        } else {
            //2、没有查到当前社交用户对应的记录我们就需要注册一个
            MemberEntity register = new MemberEntity();
            //社交gitee登录的id作为会员id
            register.setId(Long.valueOf(giteeUserId));
            register.setSocialName(giteeUser.getName());
            register.setUsername(giteeUser.getName());
            register.setNickname(giteeUser.getName());
            register.setCreateTime(new Date());
            register.setSocialBio(giteeUser.getBio());
            register.setSocialId(giteeUserId);
            //把用户信息插入到数据库中
            this.baseMapper.insert(register);
            return register;

        }

    }
package com.saodai.saodaimall.member.entity;

import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;

import java.io.Serializable;
import java.util.Date;

/**
 * 会员
 */
@Data
@TableName("ums_member")
public class MemberEntity implements Serializable {
	private static final long serialVersionUID = 1L;

	/**
	 * id
	 */
	@TableId
	private Long id;
	/**
	 * 会员等级id
	 */
	private Long levelId;
	/**
	 * 用户名
	 */
	private String username;
	/**
	 * 密码
	 */
	private String password;
	/**
	 * 昵称
	 */
	private String nickname;
	/**
	 * 手机号码
	 */
	private String mobile;
	/**
	 * 邮箱
	 */
	private String email;
	/**
	 * 头像
	 */
	private String header;
	/**
	 * 性别
	 */
	private Integer gender;
	/**
	 * 生日
	 */
	private Date birth;
	/**
	 * 所在城市
	 */
	private String city;
	/**
	 * 职业
	 */
	private String job;
	/**
	 * 个性签名
	 */
	private String sign;
	/**
	 * 用户来源
	 */
	private Integer sourceType;
	/**
	 * 积分
	 */
	private Integer integration;
	/**
	 * 成长值
	 */
	private Integer growth;
	/**
	 * 启用状态
	 */
	private Integer status;
	/**
	 * 注册时间
	 */
	private Date createTime;

	/**
	 * 社交登录用户的ID
	 */
	private String socialId;

	/**
	 * 社交登录用户的名称
	 */
	private String socialName;

	/**
	 * 社交登录用户的自我介绍
	 */
	private String socialBio;

}

整合SpringSession来解决session不同步不共享的问题(亮点2)

使用SpringSession的目的是来解决分布式session不同步不共享的问题。使用SpringSession可以把session都存在redis中,这样就解决了session不同步的问题,然后扩大作用域,这就解决了session不共享的问题,SpringSession不需要显性的操作(也就是不需要用StringRedisTemplate类的方法来把session放到redis中去,啥都不用干,就正常的把数据放到HttpSession中就可),由于整合了SpringSession,所以放到HttpSession中的数据会自动的放到redis中去,由于配置了序列化,所以session会被序列化json字符串放到redis中去,然后前端某个服务要取这个session的时候也会自动的redis中取


注意:由于这里使用springsession的用的类型是redis,所以这springsession和redis都要一起加入依赖和配置(所以session会被存到Redis缓存中)

(1)导入依赖

<!-- 整合springsession 来解决分布式session不同步不共享的问题-->
<dependency>
  <groupId>org.springframework.session</groupId>
  <artifactId>spring-session-data-redis</artifactId>
</dependency>
<!-- 整合redis-->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

(2)在application.properties配置文件里配置springsession

#配置springsession
spring.session.store-type=redis
server.servlet.session.timeout=30m
#配置redis的ip地址
spring.redis.host=192.168.241.128

(3)在config配置中加入springSession配置类

package com.saodai.saodaimall.order.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.session.web.http.CookieSerializer;
import org.springframework.session.web.http.DefaultCookieSerializer;


/**
* springSession配置类(所有要使用session的服务的session配置要一致)
*/

@Configuration
public class GulimallSessionConfig {
    
    /**
    * 配置session(主要是为了放大session作用域)
    * @return
    */
    @Bean
    public CookieSerializer cookieSerializer() {
        
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        
        //放大作用域
        cookieSerializer.setDomainName("saodaimall.com");
        cookieSerializer.setCookieName("SAODAISESSION");
        
        return cookieSerializer;
    }
    
    
    /**
    * 配置Session放到redis存储的格式为json(其实就是json序列化)
    * @return
    */
    @Bean
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }
    
}

(4)在启动类上添加@EnableRedisHttpSession注解

package com.saodai.saodaimall.order;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;

/**
 * 订单服务启动类
 */
@EnableFeignClients
@EnableRedisHttpSession
@EnableDiscoveryClient
@SpringBootApplication
public class SaodaimallOrderApplication {

    public static void main(String[] args) {
        SpringApplication.run(SaodaimallOrderApplication.class, args);
    }

}

SpringSession的原理

Spring-Session的实现就是设计一个过滤器SessionRepositoryFilter,每当有请求进入时,过滤器会首先将ServletRequest 和ServletResponse 这两个对象转换成Spring内部的包装类SessionRepositoryRequestWrapper和SessionRepositoryResponseWrapper对象,它使用了一个SessionRepositoryRequestWrapper类接管了Http Session并重写了getSession方法来实现了session的创建和管理工作。将原本需要由web服务器创建会话的过程转交给Spring-Session进行创建,本来创建的会话保存在Web服务器内存中,通过Spring-Session创建的会话信息可以保存第三方的服务中,如:redis,mysql等。Web服务器之间通过连接第三方服务来共享数据,实现Session共享!

@Order(SessionRepositoryFilter.DEFAULT_ORDER)
public class SessionRepositoryFilter<S extends Session> extends OncePerRequestFilter {
    @Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        
		request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);
         //使用HttpServletRequest 、HttpServletResponse和servletContext创建一个SessionRepositoryRequestWrapper
		SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
				request, response, this.servletContext);
		SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
				wrappedRequest, response);
		try {
			filterChain.doFilter(wrappedRequest, wrappedResponse);
		}
		finally {
            //保存session信息
			wrappedRequest.commitSession();
		}
	}
}
@Override
		public HttpSessionWrapper getSession(boolean create) {
            //获取当前Request作用域中代表Session的属性,缓存作用避免每次都从sessionRepository获取
			HttpSessionWrapper currentSession = getCurrentSession();
			if (currentSession != null) {
				return currentSession;
			}
            //查找客户端中一个叫SESSION的cookie,拿到sessionId,通过sessionRepository对象根据sessionId去Redis中查找
			S requestedSession = getRequestedSession();
            //如果从redis中查询到了值
			if (requestedSession != null) {
                //客户端存在sessionId 并且未过期
				if (getAttribute(INVALID_SESSION_ID_ATTR) == null) {
					requestedSession.setLastAccessedTime(Instant.now());
					this.requestedSessionIdValid = true;
					currentSession = new HttpSessionWrapper(requestedSession, getServletContext());
					currentSession.setNew(false);
                    //将Session设置到request属性中
					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");
			}
            //不创建Session就直接返回null
			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)"));
			}
            //执行到这了说明需要创建新的Session
            // 通过sessionRepository创建RedisSession这个对象
			S session = SessionRepositoryFilter.this.sessionRepository.createSession();
			session.setLastAccessedTime(Instant.now());
			currentSession = new HttpSessionWrapper(session, getServletContext());
			setCurrentSession(currentSession);
			return currentSession;
		}
	// 通过sessionRepository创建RedisSession这个对象
	@Override
	public RedisSession createSession() {
		Duration maxInactiveInterval = Duration
				.ofSeconds((this.defaultMaxInactiveInterval != null)
						? this.defaultMaxInactiveInterval
						: MapSession.DEFAULT_MAX_INACTIVE_INTERVAL_SECONDS);
		RedisSession session = new RedisSession(maxInactiveInterval);
		session.flushImmediateIfNecessary();
		return session;
	}

好文参考:Spring-Session实现session共享原理及解析_五霸哥的博客-CSDN博客_session共享如何实现

小技术

使用视图映射器来实现页面跳转

传统写法是在控制器里实现

自定义一个配置类来实现WebMvcConfigurer接口,然后重写addViewControllers方法来增加视图映射器

使用配置文件来动态配置属性值

这样就可以通过在配置文件里修改对应的值来改变属性值,核心注解是@ConfigurationProperties(prefix=""),@Data注解也要加

使用Feign远程调用服务

添加openFeign依赖并且在启动了通过@EnableFeignClients注解开启远程调用端即可用feign远程调用服务

定义一个远程调用的接口,通过@FeignClient注解来指定调用哪个服务,把第三方服务控制器的方法签名拿过来即可,注意路径一定要写对,特别是如果有父路径不要忘了写

在需要远程调用的服务器里注入刚写的远程接口,然后调用就可,例如这里是认证中心调用第三方服务的发生验证码的接口

使用异常机制

 /**
     *    会员注册
     */

    @Override
    public void register(MemberUserRegisterVo vo) {

        MemberEntity memberEntity = new MemberEntity();

        //设置默认等级
        MemberLevelEntity levelEntity = memberLevelDao.getDefaultLevel();
        memberEntity.setLevelId(levelEntity.getId());

        //设置其它的默认信息
        //检查用户名和手机号是否唯一。感知异常,异常机制(异常机制就是问题就抛出具体异常,没问题就继续执行下面的语句)
        checkPhoneUnique(vo.getPhone());
        checkUserNameUnique(vo.getUserName());

        memberEntity.setNickname(vo.getUserName());
        memberEntity.setUsername(vo.getUserName());
        //密码进行MD5盐值加密(盐值加密同一个数据的每次加密结果是不一样的,通过match方法来密码校验)
        // (注意这里不能用md5直接加密放数据库,因为彩虹表可以破解md5,所谓彩虹表就是通过大量的md5数据反向退出md5
        // 注意MD5是不可逆,但是可暴力通过彩虹表破解)
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String encode = bCryptPasswordEncoder.encode(vo.getPassword());
        memberEntity.setPassword(encode);
        memberEntity.setMobile(vo.getPhone());
        memberEntity.setGender(0);
        memberEntity.setCreateTime(new Date());

        //保存数据
        this.baseMapper.insert(memberEntity);
    }

    /**
     * 检查手机号是否重复的异常机制方法
     * @param phone
     * @throws PhoneException
     */
    @Override
    public void checkPhoneUnique(String phone) throws PhoneException {

        Long phoneCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("mobile", phone));
        //usernameCount > 0表示手机号已经存在
        if (phoneCount > 0) {
            throw new PhoneException();
        }

    }

    /**
     * 检查用户名是否重复的异常机制方法
     * @param userName
     * @throws UsernameException
     */
    @Override
    public void checkUserNameUnique(String userName) throws UsernameException {

        Long usernameCount = this.baseMapper.selectCount(new QueryWrapper<MemberEntity>().eq("username", userName));
        //usernameCount > 0表示用户名已经存在
        if (usernameCount > 0) {
            throw new UsernameException();
        }
    }

上面定义检查用户名和电话号码的异常机制方法的具体实现

  /**
     * 会员注册功能
     * @param vo
     * @return
     */
    @PostMapping(value = "/register")
    public R register(@RequestBody MemberUserRegisterVo vo) {

        try {
            memberService.register(vo);
        } catch (PhoneException e) {
            //BizCodeEnum.PHONE_EXIST_EXCEPTION=存在相同的手机号  15002
            return R.error(BizCodeEnum.PHONE_EXIST_EXCEPTION.getCode(),BizCodeEnum.PHONE_EXIST_EXCEPTION.getMessage());
        } catch (UsernameException e) {
            //BizCodeEnum.USER_EXIST_EXCEPTION=商品库存不足  21000
            return R.error(BizCodeEnum.USER_EXIST_EXCEPTION.getCode(),BizCodeEnum.USER_EXIST_EXCEPTION.getMessage());
        }

        return R.ok();
    }

使用异常机制的原因就是希望控制器能够发现并处理异常

package com.saodai.saodaimall.member.exception;


public class UsernameException extends RuntimeException {
    
    
    public UsernameException() {
        super("存在相同的用户名");
    }
}
package com.saodai.saodaimall.member.exception;


public class PhoneException extends RuntimeException {

    public PhoneException() {
        super("存在相同的手机号");
    }
}

把上面两个单独的异常抽取出来封装成异常类

使用MD5盐值加密

加密

先创建一个加密器BCryptPasswordEncoder,然后调用他的encode方法把需要加密的密码放进去就会自动生成一串加密后的值

注意同一个密码每次生成的值是不一样的

解密

从数据库里面拿到加密的数据后调用matches方法就可以匹配两个密码是否一致,如果一致那就返回true,不一致返回false,前面是password是旧密码(没加密的密码),后面的passwordDb是数据库加密的密码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/61030.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

HTML静态网页作业——关于我的家乡介绍安庆景点

家乡旅游景点网页作业制作 网页代码运用了DIV盒子的使用方法&#xff0c;如盒子的嵌套、浮动、margin、border、background等属性的使用&#xff0c;外部大盒子设定居中&#xff0c;内部左中右布局&#xff0c;下方横向浮动排列&#xff0c;大学学习的前端知识点和布局方式都有…

Armbian搭建本地Gitea服务器

Armbian搭建本地Gitea服务器 1 安装Docker Docker 是一个用于开发、发布和运行应用程序的开放平台。 Docker 是一个开源的应用容器引擎&#xff0c;Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&…

R语言中的prophet预测时间序列数据模型

本文 将针对R进行的几次建模练习的结果&#xff0c;以魁北克数据为依据&#xff0c;分为13年的训练和1年的测试。prophet与基本线性模型&#xff08;lm&#xff09;&#xff0c;一般加性模型&#xff08;gam&#xff09;和随机森林&#xff08;randomForest&#xff09;进行了比…

ES6:ES6 的新增语法

什么是 ES6 ? ES 的全称是 ECMAScript , 它是由 ECMA 国际标准化组织,制定的一项脚本语言的标准化规范。 年份 版本 2015年6月 ES2015 2016年6月 ES2016 2017年6月 ES2017 2018年6月 ES2018 … … ES6 实际上是一个泛指&#xff0c;泛指 ES2015 及后续的版本。 …

基于最大熵图像插值Maximum Entropy插值算法的图像超分辨重构研究-附Matlab代码

⭕⭕ 目 录 ⭕⭕✳️ 一、引言✳️ 二、图像复原基本原理✳️ 三、最大熵图像插值原理✳️ 四、实验验证✳️ 五、参考文献✳️ 六、Matlab程序获取与验证✳️ 一、引言 图像是一种表达信息的形式&#xff0c;其中&#xff0c;数字图像反馈的信息更加丰富。 在获取图像的过程中…

基于N32G45的按键驱动

基于N32G45的按键驱动 1.N32G45简介 N32G45系列集成了最新一代嵌入式ARM Cortex™-M4F处理器&#xff0c;在Cortex™-M3内核的基础上强化了运算能力、新增加了浮点运算处理单元&#xff08;FPU&#xff09;、DSP和并行计算指令&#xff0c;提供1.25DMIPS/MHz的优异性能。同时其…

JAVA复习【11】单列集合Collection:ArrayList、 LinkedList、HashSet、TreeSet学习与使用

1.首先思考一个问题&#xff1a;为什么要有集合&#xff1f; 我们也知道&#xff0c;数组可以保存多个对象&#xff0c;但是在某些情况下无法确定到底需要保存多少个对象&#xff0c;此时数组不再适用没因为数组的长度不可变&#xff0c;例如&#xff0c;要保存一个学校的学生信…

移动WEB开发之rem布局--less基础

维护 css 的弊端 CSS 是一门非程序式语言&#xff0c;没有变量、函数、SCOPE&#xff08;作用域&#xff09;等概念。 CSS 需要书写大量看似没有逻辑的代码&#xff0c;CSS 冗余度是比较高的。 不方便维护及扩展&#xff0c;不利于复用。 CSS 没有很好的计算能力 非前端开…

前馈神经网络与支持向量机实战 --- 手写数字识别

前馈神经网络与支持向量机实战 — 手写数字识别 文章目录前馈神经网络与支持向量机实战 --- 手写数字识别一、前馈神经网络介绍二、支持向量机介绍三、数据集说明四、环境准备五、实验要求六、Python代码tutorial_minst_fnn-keras.py&#xff1a;使用TensorFlow的Sequential实现…

Linux开发常用ps命令选项详解

【摘要】本文介绍了在Linux应用/内核开发调试中&#xff0c;经常需要用到的两个选项组合&#xff0c;当然&#xff0c;如果你需要查看更多更详尽的选项说明&#xff0c;可以参考man说明文档&#xff0c;即命令行下输入man ps进行查看。 aux选项组合 使用场景&#xff1a;更多…

算法日常训练12.4(最接近目标价格甜点成本)

只能说回溯实在是诡异&#xff0c;刚看到这题目思路一点不清晰&#xff0c;想着用回溯想到一点写一点&#xff0c;就这样诡异的出来了。 主要回溯思想&#xff0c;由于冰淇淋基料只能选一种&#xff0c;那就对数组遍历&#xff0c;每次对一种冰淇淋基料继续回溯&#xff0c;用r…

Proxmox VE 修改集群名称

作者&#xff1a;田逸&#xff08;formyz) Proxmox VE集群一旦创建&#xff0c;其集群的名称就固定下来。在Proxmox VE Web管理后台&#xff0c;没有相应的菜单或按钮对应与集群名称的修改&#xff08;仅仅发现修改虚拟机选项有修改名称的地方&#xff09;。在宿主系统Debian下…

网课题库接口

网课题库接口 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 题库&#xff1a;题库后台&#xff08;点击跳转&#xff09; 题库…

[附源码]计算机毕业设计文曦家教预约系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

java计算机毕业设计ssm社团管理系统9e73v(附源码、数据库)

java计算机毕业设计ssm社团管理系统9e73v&#xff08;附源码、数据库&#xff09; 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。…

计及新能源出力不确定性的电气设备综合能源系统协同优化(Matlab代码实现)

目录 第一部分 文献一《计及新能源出力不确定性的电气设备综合能源系统协同优化》 0 引言 &#xff11; 新能源出力不确定性处理 1.1 新能源出力预测误差分布 1.2 新能源出力的时间相关性 1.3 场景生成 &#xff12; 计及温控负荷调节能力的电气综合能源系统协同优化建模…

[附源码]计算机毕业设计文具商城购物系统Springboot程序

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【计算机毕业设计】基于JSP的毕业设计选题系统的设计与实现

分类号&#xff1a;TP315 U D C&#xff1a;D10621-408-(2007) 498-0 密 级&#xff1a;公 开 编 号&#xff1a;2002211105 成都信息工程学院 学位论文 基于JSP的毕业设计选题系统的设计与实现 摘 要 随着全球信息化技术的兴起&#xff0c;特别是Internet的日益普及&…

python -- PyQt5(designer)中文详细教程(四)事件和信号

事件 signals and slots也 被其他⼈翻译成信号和槽机制。 所有的应用都是事件驱动的。事件大部分都是由用户的行为产⽣的&#xff0c;当然也有其他的事件产生方式&#xff0c;比如网络的连接&#xff0c;窗口管理器或者定时器等。调⽤应⽤的exec_()⽅法时&#xff0c;应⽤会进⼊…

Python金融领域人工智能教程

Python金融领域人工智能教程 财务分析、时间序列分析、投资组合优化、CAPM、算法交易、Q-Learning 等等&#xff01; 课程英文名&#xff1a;Financial Engineering and Artificial Intelligence in Python 此视频教程共6.0小时&#xff0c;中英双语字幕&#xff0c;画质清晰…