旧版SpringSecurity和JWT实现认证和授权
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了旧版SpringSecurity和JWT实现认证和授权相关的知识,希望对你有一定的参考价值。
参考技术A SpringSecurity 是一个强大的可高度定制的 认证 和 授权 框架,对于 Spring 应用来说它是一套 Web 安全标准。 SpringSecurity 注重于为Java应用提供认证和授权功能,像所有的 Spring 项目一样,它对自定义需求具有强大的扩展性。JWT 是 JSON WEB TOKEN 的缩写,它是基于 RFC 7519 标准定义的一种可以安全传输的的 JSON 对象,由于使用了数字签名,所以是可信任和安全的。
JWT token 的格式: header.payload.signature
header 中用于存放签名的生成算法: "alg":"HS512"
payload 中用于存放 用户名 、 token 的生成时间和过期时间
signature 为以 header 和 payload 生成的签名,一旦 header 和 payload 被篡改,验证将失败
这是一个 JWT 的字符串
可以在该网站上获得解析结果: https://jwt.io/
用户调用登录接口,登录成功后获取到 JWT 的 token ;
之后用户每次调用接口都在 http 的 header 中添加一个叫 Authorization 的头,值为 JWT 的 token ;
后台程序通过对 Authorization 头中信息的解码及数字签名校验来获取其中的用户信息,从而实现认证和授权。
在 pom.xml 中添加项目依赖
用于生成和解析 JWT token 的工具类
相关方法说明:
在用户名和密码校验前添加的过滤器,如果请求中有jwt的token且有效,会取出token中的用户名,然后调用SpringSecurity的API进行登录操作。
第十一篇SpringSecurity基于JWT实现Token的处理
SpringSecurity基于JWT实现Token的处理
前面介绍了手写单点登录和JWT的应用,本文结合SpringSecurity来介绍下在SpringBoot项目中基于SpringSecurity作为认证授权框架的情况下如何整合JWT来实现Token的处理。
一、认证思路分析
SpringSecurity主要是通过过滤器来实现功能的!我们要找到SpringSecurity实现认证和校验身份的过滤器!
1.回顾集中式认证流程
用户认证
:
使用 UsernamePasswordAuthenticationFilter
过滤器中 attemptAuthentication
方法实现认证功能,该过滤器父类中 successfulAuthentication
方法实现认证成功后的操作。认证失败是在 unsuccessfulAuthentication
身份校验
:
使用 BasicAuthenticationFilter
过滤器中 doFilterInternal
方法验证是否登录,以决定能否进入后续过滤器。
2.分析分布式认证流程
用户认证
:
由于分布式项目,多数是前后端分离的架构设计,我们要满足可以接受异步post的认证请求参数,需要修改UsernamePasswordAuthenticationFilter过滤器中attemptAuthentication方法,让其能够接收请求体。
另外,默认successfulAuthentication方法在认证通过后,是把用户信息直接放入session就完事了,现在我们需要修改这个方法,在认证通过后生成token并返回给用户。
身份校验
:
原来BasicAuthenticationFilter过滤器中doFilterInternal方法校验用户是否登录,就是看session中是否有用户信息,我们要修改为,验证用户携带的token是否合法,并解析出用户信息,交给SpringSecurity,以便于后续的授权功能可以正常使用。
二、具体实现
1.创建项目
创建一个SpringBoot项目.引入必要的依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.bobo</groupId>
<artifactId>security-jwt-common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.80</version>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.4.0</version>
</dependency>
</dependencies>
2.JWT工具类
引入前面创建的JWT的工具类。
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.security.SignatureException;
import java.util.Calendar;
import java.util.Map;
public class JWTUtils
// 秘钥
private static final String SING = "123qwaszx";
/**
* 生成Token header.payload.sing 组成
* @return
*/
public static String getToken(Map<String,String> map)
Calendar instance = Calendar.getInstance();
instance.add(Calendar.DATE,7); // 默认过期时间 7天
JWTCreator.Builder builder = JWT.create();
// payload 设置
map.forEach((k,v)->
builder.withClaim(k,v);
);
// 生成Token 并返回
return builder.withExpiresAt(instance.getTime())
.sign(Algorithm.HMAC256(SING));
/**
* 验证Token
* @return
* DecodedJWT 可以用来获取用户信息
*/
public static DecodedJWT verify(String token)
// 如果不抛出异常说明验证通过,否则验证失败
DecodedJWT verify = null;
try
verify = JWT.require(Algorithm.HMAC256(SING)).build().verify(token);
catch (SignatureVerificationException e)
e.printStackTrace();
catch (AlgorithmMismatchException e)
e.printStackTrace();
catch (Exception e)
e.printStackTrace();
return verify;
3.用户实例
创建用户的实例,添加必要的属性
@Data
public class UserPojo implements UserDetails
private Integer id;
private String username;
private String password;
private Integer status;
@JsonIgnore
@Override
public Collection<? extends GrantedAuthority> getAuthorities()
List<SimpleGrantedAuthority> auth = new ArrayList<>();
auth.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
return auth;
@Override
public String getPassword()
return this.password;
@Override
public String getUsername()
return this.username;
@JsonIgnore
@Override
public boolean isAccountNonExpired()
return true;
@JsonIgnore
@Override
public boolean isAccountNonLocked()
return true;
@JsonIgnore
@Override
public boolean isCredentialsNonExpired()
return true;
@JsonIgnore
@Override
public boolean isEnabled()
return true;
4.UserService
完成基于SpringSecurity的数据库认证。创建UserService接口并实现
public interface UserService extends UserDetailsService
@Service
public class UserServiceImpl implements UserService
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException
UserPojo userPojo = new UserPojo();
if("zhang".equals(username))
userPojo.setUsername("zhang");
userPojo.setPassword("$2a$10$hbMJRuxJoa6kWcfeT7cNPOGdoEXm5sdfSm5DQtp//2cmCF0MHO8b6");
return userPojo;
return userPojo;
5.自定义认证过滤器
在SpringSecurity中的认证是通过UsernamePasswordAuthenticationFilter来处理的,现在我们要通过JWT来处理,那么我们就需要重写其中几个处理的方法
5.1 认证的方法
认证的逻辑还是走的UserService处理,但是我们需要自己来手动的调用认证逻辑。
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException
UserPojo sysUser = null;
try
sysUser = JSON.parseObject(getJson(request), UserPojo.class);
catch (IOException e)
e.printStackTrace();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());
this.setDetails(request, authRequest);
return authenticationManager.authenticate(authRequest);
public String getJson(HttpServletRequest request) throws IOException
BufferedReader streamReader = new BufferedReader( new InputStreamReader(request.getInputStream(), "UTF-8"));
StringBuilder sb = new StringBuilder();
String inputStr;
while ((inputStr = streamReader.readLine()) != null)
sb.append(inputStr);
return sb.toString();
5.2 认证成功
认证成功生成Token信息,并保存在响应的header头中。
@Override
protected void successfulAuthentication(HttpServletRequest request
, HttpServletResponse response
, FilterChain chain
, Authentication authResult) throws IOException, ServletException
// 生成Token信息
Map<String,String> map = new HashMap<>();
map.put("username",authResult.getName());
Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
List<String> list = new ArrayList<>();
for (GrantedAuthority authority : authorities)
list.add(authority.getAuthority());
map.put("roles", JSON.toJSONString(list));
String token = JWTUtils.getToken(map);
response.addHeader("Authorization","Bearer"+token);
try
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter out = response.getWriter();
Map<String,Object> resultMap = new HashMap();
resultMap.put("code", HttpServletResponse.SC_OK);
resultMap.put("msg", "认证通过!");
out.write(JSON.toJSONString(resultMap));
out.flush();
out.close();
catch (Exception outEx)
outEx.printStackTrace();
5.3 认证失败
认证失败会调用 unsuccess… 方法来处理,那么在这儿我们就需要直接响应了
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException
try
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter out = response.getWriter();
Map resultMap = new HashMap();
resultMap.put("code", HttpServletResponse.SC_UNAUTHORIZED);
resultMap.put("msg", "用户名或密码错误!");
out.write(new ObjectMapper().writeValueAsString(resultMap));
out.flush();
out.close();
catch (Exception outEx)
outEx.printStackTrace();
完整代码:
package com.bobo.jwt.filter;
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter
private AuthenticationManager authenticationManager;
public TokenLoginFilter(AuthenticationManager authenticationManager)
this.authenticationManager = authenticationManager;
/**
* 具体认证的方法
* @param request
* @param response
* @return
* @throws AuthenticationException
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException
UserPojo sysUser = null;
try
sysUser = JSON.parseObject(getJson(request), UserPojo.class);
catch (IOException e)
e.printStackTrace();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(sysUser.getUsername(), sysUser.getPassword());
this.setDetails(request, authRequest);
return authenticationManager.authenticate(authRequest);
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException
try
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
PrintWriter out = response.getWriter();
Map resultMap = new HashMap();
resultMap.put("code", HttpServletResponse.SC_UNAUTHORIZED);
resultMap.put("msg", "用户名或密码错误!");
out.write(new ObjectMapper().writeValueAsString(resultMap));
out.flush();
out.close();
catch (Exception outEx)
outEx.printStackTrace();
public String getJson(HttpServletRequest request) throws IOException
BufferedReader streamReader = new BufferedReader( new InputStreamReader(request.getInputStream(), "UTF-8"));
StringBuilder sb = new StringBuilder();
String inputStr;
while ((inputStr = streamReader.readLine()) != null)
sb.append(inputStr);
return sb.toString();
/**
* 登录成功后的处理
* @param request
* @param response
* @param chain
* @param authResult
* @throws IOException
* @throws ServletException
*/
@Override
protected void successfulAuthentication(HttpServletRequest request
, HttpServletResponse response
, FilterChain chain
, Authentication authResult) throws IOException, ServletException
// 生成Token信息
Map<String,String> map = new HashMap<>();
map.put("username",authResult.getName());
Collection<? extends GrantedAuthority> authorities = authResult.getAuthorities();
List<String> list = new ArrayList<>();
for (GrantedAuthority authority : authorities)
list.add(authority.getAuthority());
map.put("roles", JSON.toJSONString(list));
String token = JWTUtils.getToken(map);
response.addHeader("Authorization","Bearer"+token);
try
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter out = response.getWriter();
Map<String,Object> resultMap = new HashMap();
resultMap.put("code", HttpServletResponse.SC_OK);
resultMap.put(以上是关于旧版SpringSecurity和JWT实现认证和授权的主要内容,如果未能解决你的问题,请参考以下文章
SpringSecurity - 整合JWT使用 Token 认证授权
第十一篇SpringSecurity基于JWT实现Token的处理
第十一篇SpringSecurity基于JWT实现Token的处理