一.什么事單點登錄?
答:單點登錄SSO(Single Sign On)說得簡單點就是在一個多系統共存的環境下,用戶在一處登錄后,就不用在其他系統中登錄,也就是用戶的一次登錄能得到其他所有系統的信任。
二.單點登錄三種常見方式:
1.session廣播機制實現:即session復制
2.使用cookie+redis實現:
(1)在項目中任何一個模塊進行登錄,登錄之后,把用戶數據放到兩個redis和cookie兩個地方:
1>redis:在key :生成唯一隨機值(ip,用戶id等),value:用戶數據
2>把redis里面生成的key值放到cookie里面。
(2)訪問項目其他模塊,發送請求帶着cookie進行發送,獲取cookie,拿着cookie值進行做事情:
1>獲取到cookie值,到redis中進行查詢,根據key值進行查詢,如果查到用戶數據就是登錄。
3.使用token實現
token:按照一定的規則生成字符串,字符串可以包含用戶信息(Token是服務端生成的一串字符串,以作客戶端進行請求的一個令牌,當第一次登錄后,服務器生成一個Token便將此Token返回給客戶端,以后客戶端只需帶上這個Token前來請求數據即可,無需再次帶上用戶名和密碼)。
實現方式:
(1)在項目的某個模塊進行登錄,登錄之后,按照一定的規則生成字符串,將用戶數據放到字符串中,將字符串進行返回:
1>可以通過cookie進行返回
2>可以通過地址欄進行返回
(2)再訪問項目中的其他模塊,每次訪問都在地址欄帶着生成的字符串,在訪問模塊里面獲取地址欄字符串,根據字符串獲取用戶信息,如果可以獲取到,就是登錄。
三.JWT是什么?
JWT:Json Web Token,是基於Json的一個公開規范,這個規范允許我們使用JWT在用戶和服務器之間傳遞安全可靠的信息,他的兩大使用場景是:認證和數據交換
使用起來就是,由服務端根據規范生成一個令牌(token),並且發放給客戶端。此時客戶端請求服務端的時候就可以攜帶者令牌,以令牌來證明自己的身份信息。
簡單來說,JWT就是已經定好了規則,可以使用JWT生成字符串,可以包含用戶信息。
四.JWT的規則
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOjYyNywiZXhwIjoxNTcwMDE0ODg1fQ.vPbQh4syxNCzkKXKPSM93LzzLqoJdzPDNeKz8tz9cFM4NzhIOdPrJcH2DG
-9-9MCUufCgrAhhGjuo85GKV4bOQ
1.JWT的頭信息:
{ 'typ': 'JWT', 'alg': 'HS256' }
2.有效載荷:主體部分(包含用戶信息)
3.簽名哈希:防偽標志
五.JWT的使用
1.引入JWT依賴
<!--JWT--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> </dependency>
2.編寫JWT工具類
package com.atguigu.commonutils; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.util.StringUtils; import javax.servlet.http.HttpServletRequest; import java.util.Date; /** * @author helen * @since 2019/10/16 */ public class JwtUtils { //定義兩個常量 //token的過期時間 public static final long EXPIRE = 1000 * 60 * 60 * 24; //秘鑰 public static final String APP_SECRET = "ukc8BDbRigUDaY6pZFfWus2jZWLPHO"; /*生成token字符串的方法*/ public static String getJwtToken(String id, String nickname){ String JwtToken = Jwts.builder() //設置token的頭信息 .setHeaderParam("typ", "JWT") .setHeaderParam("alg", "HS256") //分類 .setSubject("guli-user") //設置token的過期時間 .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) //設置token的主體信息,存儲用戶信息 .claim("id", id) .claim("nickname", nickname) //設置簽名哈希 .signWith(SignatureAlgorithm.HS256, APP_SECRET) .compact(); return JwtToken; } /** * 判斷token是否存在與有效 * @param jwtToken * @return */ public static boolean checkToken(String jwtToken) { if(StringUtils.isEmpty(jwtToken)) return false; try { Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * 判斷token是否存在與有效 * @param request * @return */ public static boolean checkToken(HttpServletRequest request) { try { String jwtToken = request.getHeader("token"); if(StringUtils.isEmpty(jwtToken)) return false; Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); } catch (Exception e) { e.printStackTrace(); return false; } return true; } /** * 根據token字符串獲取會員id * @param request * @return */ public static String getMemberIdByJwtToken(HttpServletRequest request) { String jwtToken = request.getHeader("token"); if(StringUtils.isEmpty(jwtToken)) { return ""; } Jws<Claims> claimsJws = Jwts.parser().setSigningKey(APP_SECRET).parseClaimsJws(jwtToken); Claims claims = claimsJws.getBody(); return (String)claims.get("id"); } }
六.登錄實現
1.編寫用戶實體類
package com.atguigu.educenter.entity; import com.baomidou.mybatisplus.annotation.FieldFill; import com.baomidou.mybatisplus.annotation.IdType; import java.util.Date; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import java.io.Serializable; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.experimental.Accessors; /** * <p> * 會員表 * </p> * * @author testjava * @since 2020-10-28 */ @Data @EqualsAndHashCode(callSuper = false) @Accessors(chain = true) @ApiModel(value="UcenterMember對象", description="會員表") public class UcenterMember implements Serializable { private static final long serialVersionUID = 1L; @ApiModelProperty(value = "會員id") @TableId(value = "id", type = IdType.ID_WORKER_STR) private String id; @ApiModelProperty(value = "微信openid") private String openid; @ApiModelProperty(value = "手機號") private String mobile; @ApiModelProperty(value = "密碼") private String password; @ApiModelProperty(value = "昵稱") private String nickname; @ApiModelProperty(value = "性別 1 女,2 男") private Integer sex; @ApiModelProperty(value = "年齡") private Integer age; @ApiModelProperty(value = "用戶頭像") private String avatar; @ApiModelProperty(value = "用戶簽名") private String sign; @ApiModelProperty(value = "是否禁用 1(true)已禁用, 0(false)未禁用") private Boolean isDisabled; @ApiModelProperty(value = "邏輯刪除 1(true)已刪除, 0(false)未刪除") private Boolean isDeleted; @ApiModelProperty(value = "創建時間") @TableField(fill = FieldFill.INSERT) private Date gmtCreate; @ApiModelProperty(value = "更新時間") @TableField(fill = FieldFill.INSERT_UPDATE) private Date gmtModified; }
2.編寫controller層
package com.atguigu.educenter.controller; import com.atguigu.commonutils.R; import com.atguigu.educenter.entity.UcenterMember; import com.atguigu.educenter.service.UcenterMemberService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; /** * <p> * 會員表 前端控制器 * </p> * * @author testjava * @since 2020-10-28 */ @RestController @RequestMapping("/educenter/member") @CrossOrigin public class UcenterMemberController { @Autowired UcenterMemberService memberService; //登錄 @PostMapping("/login") public R userLogin(@RequestBody UcenterMember member){ //使用service中的方法實現登錄 //返回token值,使用jwt生成 String token = memberService.login(member); return R.ok().data("token",token); } //注冊 }
3.編寫service層
package com.atguigu.educenter.service; import com.atguigu.educenter.entity.UcenterMember; import com.baomidou.mybatisplus.extension.service.IService; /** * <p> * 會員表 服務類 * </p> * * @author testjava * @since 2020-10-28 */ public interface UcenterMemberService extends IService<UcenterMember> { //實現登錄 String login(UcenterMember member); }
4.編寫serviceImpl
package com.atguigu.educenter.service.impl; import com.atguigu.commonutils.JwtUtils; import com.atguigu.commonutils.MD5; import com.atguigu.educenter.entity.UcenterMember; import com.atguigu.educenter.mapper.UcenterMemberMapper; import com.atguigu.educenter.service.UcenterMemberService; import com.atguigu.servicebase.exceptionhandler.GuliException; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; /** * <p> * 會員表 服務實現類 * </p> * * @author testjava * @since 2020-10-28 */ @Service public class UcenterMemberServiceImpl extends ServiceImpl<UcenterMemberMapper, UcenterMember> implements UcenterMemberService { //實現登錄 @Override public String login(UcenterMember member) { //獲取手機號和密碼 String mobile = member.getMobile(); String password = member.getPassword(); //判斷手機號和密碼是否為空 if(StringUtils.isEmpty(mobile)|| StringUtils.isEmpty(password)){ throw new GuliException(20001,"手機號或者密碼為空,登錄失敗"); } //判斷手機號是否正確 QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>(); wrapper.eq("mobile",mobile); UcenterMember mobileMember = baseMapper.selectOne(wrapper); //判斷查詢的對象是否為空 if(mobileMember==null){//沒有這個手機號 throw new GuliException(20001,"手機號不存在,登錄失敗"); } //判斷密碼是否正確 /**因為數據庫中的密碼是加密的, * 所以需要先將傳入的密碼進行加密, * 再和數據庫的密碼進行比較, * 使用MD5加密*/ if(!MD5.encrypt(password).equals(mobileMember.getPassword())){ throw new GuliException(20001,"密碼不正確,登錄失敗"); } //判斷用戶是否禁用 if(mobileMember.getIsDisabled()){ throw new GuliException(20001,"用戶已禁用,登錄失敗"); } //登錄成功,使用jwt工具類生成token String jwtToken = JwtUtils.getJwtToken(mobileMember.getId(), mobileMember.getNickname()); return jwtToken; } }
5.swagger測試
六.注冊功能
1.編寫實體類
package com.atguigu.educenter.entity.vo; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.Data; /** * author LiQinZhen * date 2020/10/29 * description: 注冊實體類 */ @Data @ApiModel(value="注冊對象", description="注冊對象") public class RegisterVo { @ApiModelProperty(value = "昵稱") private String nickname; @ApiModelProperty(value = "手機號") private String mobile; @ApiModelProperty(value = "密碼") private String password; @ApiModelProperty(value = "驗證碼") private String code; }
2.在controller中創建注冊的方法
//注冊 @RequestMapping("/register") public R registerUser(@RequestBody RegisterVo registerVo){ memberService.register(registerVo); return R.ok(); }
3.在service創建注冊方法
public interface UcenterMemberService extends IService<UcenterMember> { //實現登錄 String login(UcenterMember member); //注冊 void register(RegisterVo registerVo); }
4.編寫service的實現類Impl
package com.atguigu.educenter.service.impl; import com.atguigu.commonutils.JwtUtils; import com.atguigu.commonutils.MD5; import com.atguigu.educenter.entity.UcenterMember; import com.atguigu.educenter.entity.vo.RegisterVo; import com.atguigu.educenter.mapper.UcenterMemberMapper; import com.atguigu.educenter.service.UcenterMemberService; import com.atguigu.servicebase.exceptionhandler.GuliException; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; /** * <p> * 會員表 服務實現類 * </p> * * @author testjava * @since 2020-10-28 */ @Service public class UcenterMemberServiceImpl extends ServiceImpl<UcenterMemberMapper, UcenterMember> implements UcenterMemberService { //注入redis模板
@Autowired RedisTemplate<String,String> redisTemplate; //實現登錄 @Override public String login(UcenterMember member) { //獲取手機號和密碼 String mobile = member.getMobile(); String password = member.getPassword(); //判斷手機號和密碼是否為空 if(StringUtils.isEmpty(mobile)|| StringUtils.isEmpty(password)){ throw new GuliException(20001,"手機號或者密碼為空,登錄失敗"); } //判斷手機號是否正確 QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>(); wrapper.eq("mobile",mobile); UcenterMember mobileMember = baseMapper.selectOne(wrapper); //判斷查詢的對象是否為空 if(mobileMember==null){//沒有這個手機號 throw new GuliException(20001,"手機號不存在,登錄失敗"); } //判斷密碼是否正確 /**因為數據庫中的密碼是加密的, * 所以需要先將傳入的密碼進行加密, * 再和數據庫的密碼進行比較, * 使用MD5加密*/ if(!MD5.encrypt(password).equals(mobileMember.getPassword())){ throw new GuliException(20001,"密碼不正確,登錄失敗"); } //判斷用戶是否禁用 if(mobileMember.getIsDisabled()){ throw new GuliException(20001,"用戶已禁用,登錄失敗"); } //登錄成功,使用jwt工具類生成token String jwtToken = JwtUtils.getJwtToken(mobileMember.getId(), mobileMember.getNickname()); return jwtToken; } //注冊方法 @Override public void register(RegisterVo registerVo) { //獲取注冊的數據 String code = registerVo.getCode();//驗證碼 String mobile = registerVo.getMobile();//手機號 String nickname = registerVo.getNickname();//昵稱 String password = registerVo.getPassword();//密碼 //非空判斷 if(StringUtils.isEmpty(code)||StringUtils.isEmpty(mobile) ||StringUtils.isEmpty(nickname)||StringUtils.isEmpty(password)){ throw new GuliException(20001,"驗證碼,手機號,昵稱或者密碼為空,注冊失敗"); } //判斷驗證碼是否正確,即發送到手機的驗證碼和數據庫存的是否一樣 //獲取redis中的驗證碼 String redisCode = redisTemplate.opsForValue().get(mobile); //比較輸入的驗證碼和redis中的驗證碼是否一樣 if(!code.equals(redisCode)){ throw new GuliException(20001,"驗證碼不正確,注冊失敗"); } //判斷手機號是否相同,如果表里面存在相同的手機號則不進行添加 //先根據手機號在數據庫查詢數據 QueryWrapper<UcenterMember> wrapper = new QueryWrapper<>(); wrapper.eq("mobile",mobile); Integer count = baseMapper.selectCount(wrapper); if(count>0){ throw new GuliException(20001,"手機號已存在,注冊失敗"); } //數據添加到數據庫 UcenterMember member = new UcenterMember(); member.setMobile(mobile); member.setNickname(nickname); member.setPassword(MD5.encrypt(password)); member.setAvatar("http://thirdwx.qlogo.cn/mmopen/vi_32/DYAIOgq83eoj0hHXhgJNOTSOFsS4uZs8x1ConecaVOB8eIl115xmJZcT4oCicvia7wMEufibKtTLqiaJeanU2Lpg3w/132"); member.setIsDisabled(false); baseMapper.insert(member); } }
5.根據token獲取用戶信息
//根據token獲取用戶信息 @GetMapping("/getMemberInfo") public R getMemberInfo(HttpServletRequest request){ //調用jwt的方法,根據request對象獲取頭信息,返回用戶id String memberId = JwtUtils.getMemberIdByJwtToken(request); //查詢數據庫,根據用戶id獲取用戶信息 UcenterMember member = memberService.getById(memberId); return R.ok().data("userInfo",member); }