JWT可以认为是一种特殊编码格式的通达信资金流向接口,token。普通oauth2颁发是一串随机hash字符串,本身无意义,而JWT的通达信资金流向接口,token有特定含义,分为三部分:
现在写一个例子,代码如下:
传统session的会话保持业务逻辑。因为通达信资金流向接口,token无状态、分布式,在通达信资金流向接口,token有效期内,原则上只要使用这个通达信资金流向接口,token,仍然可以访问系统。通达信资金流向接口,token续签。围绕通达信资金流向接口,token续签设计系统架构会增加额外的复杂度。可以用redis记录通达信资金流向接口,token状态,在用户访问后更新状态,但这就是通达信资金流向接口,token机制用“歪”了,JWT-通达信资金流向接口,token的无状态此时变成有状态了,而这恰恰就是传统session+cookie机制可以覆盖住的业务场景。考虑系统的拓展和高可用,可以考虑使用成熟的springsession框架。
加入后,spring接受的http请求,会被JwtRequestFilter过滤一次,JwtRequestFilter的过滤核心是doFilterInternal函数。doFilterInternal对于每一次的http请求,均会提取http头部header字段里面的key为Authorization对应的值,如果对应的值非空,且以Bearer开头,则意味是通达信资金流向接口,token认证,那么就走通达信资金流向接口,token认证逻辑。代码将会根据传入的通达信资金流向接口,token逆向的通过工具类jwtTokenUtil反向找出用户名,然后根据用户名判断当前用户是否已被授权,如果没有被授权,jwtTokenUtil验证客户端传入的通达信资金流向接口,token和后端系统的通达信资金流向接口,token信息是否一致,一致则把具有该通达信资金流向接口,token的客户端记录进springsecurity授权记录中,从此,凡是具有该通达信资金流向接口,token的访问,均放行通过。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;
@RestController
public class JwtAuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private JwtUserDetailsService jwtInMemoryUserDetailsService;
/**
* 验证用户名和密码。
* 如果验证通过,创建通达信资金流向接口,token并将其返回给客户端
*
* @param request
* @return
*/
@RequestMapping(value = '/auth', method = RequestMethod.POST)
public ResponseEntity> createAuthenticationToken(@RequestBody JwtRequest request) {
String username = request.getUsername();
String password = request.getPassword();
System.out.println('用户名:' + username);
System.out.println('密码:' + password);
UsernamePasswordAuthenticationToken authentication=new UsernamePasswordAuthenticationToken(username, password);
authenticationManager.authenticate(authentication);
UserDetails userDetails = jwtInMemoryUserDetailsService.loadUserByUsername(request.getUsername());
String 通达信资金流向接口,token = jwtTokenUtil.generateToken(userDetails);
return ResponseEntity.ok(new JwtResponse(通达信资金流向接口,token));
}
@RequestMapping(value='/api')
public Map api() {
Map map=new HashMap();
map.put('page', 'api');
return map;
}
@RequestMapping(value='/index')
public Map index() {
Map map=new HashMap();
map.put('page', 'index');
return map;
}
@RequestMapping(value='/home')
public Map home() {
Map map=new HashMap();
map.put('page', 'home');
return map;
}
}
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 拒绝每个没有通过身份验证的请求并发送错误代码401。
*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
System.out.println('未获得授权');
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, 'Unauthorized');
}
}
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
/**
* 注意,此处是明文,实际场景时候要加密
*/
@Component
public class JwtPasswordEncoder extends BCryptPasswordEncoder {
@Override
public String encode(CharSequence rawPassword) {
return rawPassword.toString();
}
@Override
public boolean matches(CharSequence rawPassword, String encodedPassword) {
if (rawPassword == null) {
throw new IllegalArgumentException('rawPassword为空!');
}
if (encodedPassword == null || encodedPassword.length() == 0) {
throw new IllegalArgumentException('encodedPassword为空');
}
return encodedPassword.equals(rawPassword);
}
}
import java.io.Serializable;
public class JwtRequest implements Serializable {
private String username;
private String password;
//JSON Parsing
public JwtRequest() {
}
public JwtRequest(String username, String password) {
this.setUsername(username);
this.setPassword(password);
}
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
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;
/**
* 对于任何一个传入的请求,都会执行doFilterInternal。
* 检查请求是否具有有效的通达信资金流向接口,token。
* 如果通达信资金流向接口,token有效,在上下文中设置Authentication,
* 表明当前用户通过身份验证。
*/
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private JwtUserDetailsService jwtUserDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String header = request.getHeader('Authorization');
String username = null;
String 通达信资金流向接口,token = null;
// 通达信资金流向接口,token一般又称之为'Bearer 通达信资金流向接口,token',以Bearer开头
// 截取纯粹的通达信资金流向接口,token
String BEARER = 'Bearer ';
if (header != null && header.startsWith(BEARER)) {
通达信资金流向接口,token = header.substring(BEARER.length());//从Bearer 之后开始截取
username = jwtTokenUtil.getUsernameFromToken(通达信资金流向接口,token);
System.out.println(username + ' 通达信资金流向接口,token:' + 通达信资金流向接口,token);
}
//拿到通达信资金流向接口,token后验证
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = jwtUserDetailsService.loadUserByUsername(username);
// 如果通达信资金流向接口,token有效,配置Spring Security授权
if (jwtTokenUtil.validateToken(通达信资金流向接口,token, userDetails)) {
System.out.println(username + ' 通达信资金流向接口,token验证通过');
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
//当前用户已经授权,授权认证信息传递给Spring Security配置.
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
if (SecurityContextHolder.getContext().getAuthentication() != null) {
System.out.println(username + ' 已授权');
}
filterChain.doFilter(request, response);
}
}
public class JwtResponse {
private String 通达信资金流向接口,token;
public JwtResponse(String 通达信资金流向接口,token) {
this.通达信资金流向接口,token = 通达信资金流向接口,token;
}
public String getToken() {
return 通达信资金流向接口,token;
}
}
import io.jsonweb通达信资金流向接口,token.Claims;
import io.jsonweb通达信资金流向接口,token.Jwts;
import io.jsonweb通达信资金流向接口,token.SignatureAlgorithm;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtTokenUtil implements Serializable {
public static final long TOKEN_VALIDITY = 60 * 60 * 5; //通达信资金流向接口,token有效期
private String secret = 'zhangphil_secret';
public String getUsernameFromToken(String 通达信资金流向接口,token) {
return getClaimFromToken(通达信资金流向接口,token, Claims::getSubject);
}
public Date getIssuedAtDateFromToken(String 通达信资金流向接口,token) {
return getClaimFromToken(通达信资金流向接口,token, Claims::getIssuedAt);
}
public Date getExpirationDateFromToken(String 通达信资金流向接口,token) {
return getClaimFromToken(通达信资金流向接口,token, Claims::getExpiration);
}
public T getClaimFromToken(String 通达信资金流向接口,token, Function claimsResolver) {
final Claims claims = getAllClaimsFromToken(通达信资金流向接口,token);
return claimsResolver.apply(claims);
}
private Claims getAllClaimsFromToken(String 通达信资金流向接口,token) {
return Jwts.parser().setSigningKey(secret).parseClaimsJws(通达信资金流向接口,token).getBody();
}
private Boolean isTokenExpired(String 通达信资金流向接口,token) {
final Date expiration = getExpirationDateFromToken(通达信资金流向接口,token);
return expiration.before(new Date());
}
private Boolean ignoreTokenExpiration(String 通达信资金流向接口,token) {
return false;
}
public String generateToken(UserDetails userDetails) {
Map claims = new HashMap<>();
return doGenerateToken(claims, userDetails.getUsername());
}
private String doGenerateToken(Map claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + TOKEN_VALIDITY * 1000)).signWith(SignatureAlgorithm.HS512, secret).compact();
}
public Boolean canTokenBeRefreshed(String 通达信资金流向接口,token) {
return (!isTokenExpired(通达信资金流向接口,token) || ignoreTokenExpiration(通达信资金流向接口,token));
}
public Boolean validateToken(String 通达信资金流向接口,token, UserDetails userDetails) {
final String username = getUsernameFromToken(通达信资金流向接口,token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(通达信资金流向接口,token));
}
}
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
@Service
public class JwtUserDetailsService implements UserDetailsService {
//正常情况下,当loadUserByUsername传入用户名后,
//应该连接数据库从数据库中根据用户名把该用户的信息取出来,
//本例出于简单演示的目的,不再额外的引入数据库,
//假设已经知道用户名和密码,硬编码写死了用户名和密码
public static final String USER_NAME = 'zhangphil';
public static final String USER_PASSWORD = '12345678';
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println(username + ' 加载信息');
if (USER_NAME.equals(username)) {
return new User(USER_NAME, USER_PASSWORD, new ArrayList<>());
} else {
throw new UsernameNotFoundException('用户不存在:' + username);
}
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
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.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class JwtWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {
/**
* 密码管理
*/
@Autowired
private JwtPasswordEncoder jwtPasswordEncoder;
@Autowired
private JwtUserDetailsService jwtUserDetailsService;
@Autowired
private JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Override
protected void configure(HttpSecurity http) throws Exception {
// 本例不需要CSRF
http.csrf().disable()
// 排除/auth。
// 对于请求授权的/auth不需要授权,放行。
.authorizeRequests()
.antMatchers('/auth').permitAll()
//其余的所有请求均需要认证授权
.anyRequest().authenticated()
.and()
.exceptionHandling()//错误处理
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
.and()
//本例不需要维护有状态的session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// 对于任何一个传入的请求加入一个通达信资金流向接口,token过滤器,验证。
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
/**
* 配置AuthenticationManager使其知道从那里加载用户认证信息
*/
@Override
public void configure(AuthenticationManagerBuilder auth) {
try {
auth.userDetailsService(jwtUserDetailsService)
.passwordEncoder(jwtPasswordEncoder);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 假设这一部分接口是公开开放的,不需要通达信资金流向接口,token即可访问。
* 这部分客户端http请求不拦截
* 排除。
*/
@Override
public void configure(WebSecurity web) {
web.ignoring()
.antMatchers(
'/index**',
'/home/**');
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SpringJwtApplication {
public static void main(String[] args) {
SpringApplication.run(SpringJwtApplication.class, args);
}
}
此后,客户端用户每一次访问受保护的资源时候,加入通达信资金流向接口,token。通达信资金流向接口,token是写到header里面的,通达信资金流向接口,token的key为Authorization,value按照协议规范,需要以字符串Bearer开头,注意Bearer后面要带上空格。
如果不使用springsecurity,那么spring里面实现接口访问即是常规的框架编程。引入springsecurity后,并在springsecurity中实现基于JWT的通达信资金流向接口,token授权-验证,那么首先需要对于访问用户发放通达信资金流向接口,token。所以在JwtAuthenticationController的createAuthenticationToken实现对于用户通达信资金流向接口,token的生成和返回。
头部Header载荷Payload签名Signature
http.addFilterBefore(jwtRequestFilter,UsernamePasswordAuthenticationFilter.class);
客户端与后端服务基于通达信资金流向接口,token的认证-授权访问流程一般情况是这样的:
然后后台返回通达信资金流向接口,token:
JwtRequestFilter类目的是配置spring的http请求,把JwtRequestFilter写完后,加入到JwtWebSecurityConfigurerAdapter类的configure(HttpSecurityhttp)函数的http里面:
把通达信资金流向接口,token复制出来,在get请求时候,加入通达信资金流向接口,token,访问/api接口,注意,需要为通达信资金流向接口,token加上头Bearer,加到header里面,key是Authorization:
系统启动后,当访问/index或者/home页面时候,不需要通达信资金流向接口,token,均正常打开:
用户侧发送用户名和密码到服务器端。服务器端收到用户名后验证用户有效性。服务器端验证通过后,发送给用户一个通达信资金流向接口,token。客户端存储通达信资金流向接口,token,并在每次请求服务器端时附带上这个通达信资金流向接口,token。后续,每一层客户端的请求到达服务器端后,服务器验证通达信资金流向接口,token的有效性,若通过验证,返回客户端所需数据。
JWT-通达信资金流向接口,token此类鉴权认证机制不太适用的场景:
当直接访问localhost:8080/api时候,页面返回HTTPERROR401错误,此时,需要post用户名和密码:
典型的应用场景是api鉴权。比如移动应用的app开发,用api,从远程服务器端拉取数据,每次的http访问,均带上通达信资金流向接口,token。
在基于通达信资金流向接口,token的客户端-服务器端认证授权以前,前端到服务器端的认证-授权通常是基于session,自从通达信资金流向接口,token机制出现并流行起来后,基于通达信资金流向接口,token的客户端-服务器端认证-授权访问机制变得越来越主要,通达信资金流向接口,token机制从某种意义上讲是过去传统session会话机制的另外一种解决方案,并尤其适用于当前的大规模微服务、分布式体系架构。
其中最关键的有三个类,JwtAuthenticationController,JwtRequestFilter,JwtWebSecurityConfigurerAdapter
每一部分用.分隔开。
当用户访问localhost:8080/auth后,JwtAuthenticationController在createAuthenticationToken里面提取用户名和密码,构造UsernamePasswordAuthenticationToken,并将UsernamePasswordAuthenticationToken记入到上下文中的authenticationManager,如果用户名和密码均正确,springsecurity就“记忆”当前访问的用户,并通过jwtInMemoryUserDetailsService加载用户信息,然后jwtTokenUtil生成通达信资金流向接口,token返回给用户。
如果在授权认证时候,传入错误的密码,比如密码错误的是123那么系统认证失败,后台日志输出:
用户名:zhangphil
密码:1234
zhangphil 加载信息
未获得授权
后台系统返回正确结果:
这三个类涉及到springboot对于通达信资金流向接口,token生成和验证的逻辑流程。结合上面的逻辑代码,简单总结一下spring+jwt这种组合框架是如何验证-生成授权通达信资金流向接口,token。
文章为作者独立观点,不代表观点