学习目标
- jwt验证
- 后端的API
- 前端登录注册页面
学习内容
前端和后端会有跨域问题,不用传统的session验证,而使用JWT方式验证。
http://localhost:3000/logout 退出springSecurity验证;
http://localhost:3000/login
后端
这节课前端后端代码都会有,登陆验证注册;
登陆的逻辑,很多url对应有很多页面,每个页面的权限不一样。
登录一次只要不关闭浏览器就可以访问其它页面。
每个URL都会对应一个controllert
以前传统的session验证方式
现在很多应用前端后端可能会有跨域的问题,如有多个端,app端,web端;现在session不太适用,因为需要把session复制多份。
即现在应用有很多服务,每个服务是一个端,想通过一个身份去登录所有的后端时,需要将sessionID复制很多分,放到多台服务器上面,比较麻烦。
JWT验证方式
JWT验证的优势:
(1) 解决跨域;
(2) 不需要在服务端存储
比如会有多个服务器端,那么只需要一个令牌就可以访问多个服务器。
原理: 原来seesion的方式,一个用户登陆成功之后,需要赋给他一个sessionID,现在给他一个JWT的Token. 服务器给他一个token之后 ,自己是不需要存储任何信息的。
将userID或者其它信息加入到JWT token里面,
简化版逻辑:
(1)密钥是存储在服务器的,对用户不公开,是一个随机字符串,自己定义的。
(2)hash函数或者加密函数,加密成一个字符串,不可逆
(3)JwtToken = userID+ 加密(hash)后的信息 返回给用户,完全存储到客户端;
未来服务器验证的时候,将接收到的信息,将第一段加上密钥进行加密,看加密后的结果是否与以前加密的信息一致
这节课目的是把session 修改为JWT
两个疑惑:
(1) 篡改数据,冒充UserID去访问,不可行,hash函数加密结果会变;
(2)JwtToken是存储在用户端本地的,如果被窃取,那就是自己的问题,活该。
token一般是存储在用户本地的,浏览器的localstory里面;
优化方法:一般给用户传两个token,一个是access-token(有效时间比较短,如5分钟) ;另一个是refresh-token(有效时间比较长14天);
当每次向服务器发送请求,
带access-token,使用有效时间比较短的令牌;
当短令牌有效期过去之后,再使用Post请求重新根据refresh-koen再获取一个新的token。
Get请求:明文不安全
Post请求: 安全
这节课的逻辑,登陆页面登录之后会获得一个JWT-Token;服务器不需要存储任何信息的,用户得到之后token存到浏览器,之后每次访问服务的时候,就带上这个token(令牌)。
JWT工具类
实现utils.JwtUtil
类,为jwt
工具类,用来创建、解析jwt token
作用:
(1)是将一个字符串加上密钥,加上有效期变成一个加密后的字符串;
(2)另外一个作用将一个令牌将userID解析出来
(1)加三个依赖到pom文件
jjwt-api
jjwt-impl
jjwt-jackson
(2)复制工具类JwtUtil类
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.UUID;
@Component
public class JwtUtil {
public static final long JWT_TTL = 60 * 60 * 1000L * 24 * 14; // 有效期14天
public static final String JWT_KEY = "SDFGjhdsfalshdfHFdsjkdsfds121232131afasdfac";
public static String getUUID() {
return UUID.randomUUID().toString().replaceAll("-", "");
}
public static String createJWT(String subject) {
JwtBuilder builder = getJwtBuilder(subject, null, getUUID());
return builder.compact();
}
private static JwtBuilder getJwtBuilder(String subject, Long ttlMillis, String uuid) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
SecretKey secretKey = generalKey();
long nowMillis = System.currentTimeMillis();
Date now = new Date(nowMillis);
if (ttlMillis == null) {
ttlMillis = JwtUtil.JWT_TTL;
}
long expMillis = nowMillis + ttlMillis;
Date expDate = new Date(expMillis);
return Jwts.builder()
.setId(uuid)
.setSubject(subject)
.setIssuer("sg")
.setIssuedAt(now)
.signWith(signatureAlgorithm, secretKey)
.setExpiration(expDate);
}
public static SecretKey generalKey() {
byte[] encodeKey = Base64.getDecoder().decode(JwtUtil.JWT_KEY);
return new SecretKeySpec(encodeKey, 0, encodeKey.length, "HmacSHA256");
}
public static Claims parseJWT(String jwt) throws Exception {
SecretKey secretKey = generalKey();
return Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(jwt)
.getBody();
}
}
(3)创建Filter工具类
实现
config.filter.JwtAuthenticationTokenFilter
类,用来验证jwt token
,如果验证成功,则将User信息
注入上下文中
import com.kob.backend.mapper.UserMapper;
import com.kob.backend.pojo.User;
import com.kob.backend.service.impl.utils.UserDetailsImpl;
import com.kob.backend.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
@Autowired
private UserMapper userMapper;
@Override
protected void doFilterInternal(HttpServletRequest request, @NotNull HttpServletResponse response, @NotNull FilterChain filterChain) throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (!StringUtils.hasText(token) || !token.startsWith("Bearer ")) {
filterChain.doFilter(request, response);
return;
}
token = token.substring(7);
String userid;
try {
Claims claims = JwtUtil.parseJWT(token);
userid = claims.getSubject();
} catch (Exception e) {
throw new RuntimeException(e);
}
User user = userMapper.selectById(Integer.parseInt(userid));
if (user == null) {
throw new RuntimeException("用户名未登录");
}
UserDetailsImpl loginUser = new UserDetailsImpl(user);
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginUser, null, null);
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
filterChain.doFilter(request, response);
}
}
(4) 配置config.SecurityConfig
类,放行登录、注册等接口
import com.kob.backend.config.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/user/account/token/", "/user/account/info/").permitAll()
.antMatchers(HttpMethod.OPTIONS).permitAll()
.anyRequest().authenticated();
http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
}
}
数据库修改
修改表
创建一个列,存储头像
存储的都是头像链接;以后图片可以存储到图床.
修改pojo,因为实体是和数据库相匹配的。
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
// 在mabatis-plus中 最好不要使用int ,而去使用对象Integer
@TableId(type = IdType.AUTO) // 目的是让id自增(mybatis-plus里面的)
private Integer id;
private String username;
private String password;
private String photo;
}
写具体业务API
实现三个API
写api的流程,三个地方
(1) 写service
里面写接口,
InfoService,LoginService, RegisterService
(2) 在service / imp
l写接口的实现
(3) 写controller
里面,调用service
RegisterService接口和 LoginService接口是公开的,没有帐号之前不能锁住
登陆成功之后就可以获取用户的信息了。
根据token获取用户信息
这里使用第一个用户的token就获取第一个用户的信息
试验: 使用第二个用户登陆验证,要更换对应的token
token =用户id+ 加密信息(用户id+随机密钥)
结果:使用谁的token就获取谁的信息
注册API
前端
这节课实现了登录效果
退出登录时只需要在本地将token删除掉就行,服务器中没有存储token信息
删除在前端操作
下节课实现注册页面