我们之前都在使用http session的方式来进行登录访问的。那么现在流行的是前后端分离的开发模式。而且我们也打算这样干。包括后期我们的小程序啊什么的,很多都不支持session的方式。那我们就使用token的模式来进行登录。
为什么要使用这个jwttoken呢?
主要有上面几点优势。安全性跟减少存储开销。好了,让我们来开始使用它吧!
在pom中添加jar的支持
io.jsonwebtoken
jjwt
0.9.1
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
/**
* @author: 胡汉三
* @date: 2020/5/26 10:11
* @description: JWT工具类
* JWT是由三段组成的,分别是header(头)、payload(负载)和signature(签名)
* 其中header中放{
* "alg": "HS512",
* "typ": "JWT"
* } 表明使用的加密算法,和token的类型==>默认是JWT
*
*/
public class JwtTokenUtils {
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
// 密钥,用于signature(签名)部分解密
private static final String PRIMARY_KEY = "HHS_fast_java_plat";
// 签发者
private static final String ISS = "HanSan.HU";
// 添加角色的key
private static final String ROLE_CLAIMS = "role";
// 过期时间是3600秒,既是1个小时
private static final long EXPIRATION = 3600L;
// 选择了记住我之后的过期时间为7天
private static final long EXPIRATION_REMEMBER = 604800L;
/**
* description: 创建Token
*
* @param username
* @param isRememberMe
* @return java.lang.String
*/
public static String createToken(String username, List roles, boolean isRememberMe) {
long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
HashMap map = new HashMap<>();
map.put(ROLE_CLAIMS, roles);
return Jwts.builder()
// 采用HS512算法对JWT进行的签名,PRIMARY_KEY是我们的密钥
.signWith(SignatureAlgorithm.HS512, PRIMARY_KEY)
// 设置角色名
.setClaims(map)
// 设置发证人
.setIssuer(ISS)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.compact();
}
/**
* description: 从token中获取用户名
*
* @param token
* @return java.lang.String
*/
public static String getUsername(String token){
return getTokenBody(token).getSubject();
}
// 获取用户角色
public static List getUserRole(String token){
return (List) getTokenBody(token).get(ROLE_CLAIMS);
}
/**
* description: 判断Token是否过期
*
* @param token
* @return boolean
*/
public static boolean isExpiration(String token){
return getTokenBody(token).getExpiration().before(new Date());
}
/**
* description: 获取
*
* @param token
* @return io.jsonwebtoken.Claims
*/
private static Claims getTokenBody(String token){
return Jwts.parser()
.setSigningKey(PRIMARY_KEY)
.parseClaimsJws(token)
.getBody();
}
}
import com.alibaba.fastjson.JSON;
import com.hzw.code.common.utils.ActionResult;
import com.hzw.code.common.utils.JwtTokenUtils;
import com.hzw.code.common.utils.ResultCodeEnum;
import com.hzw.code.security.model.CustomUserDetails;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
/**
* @author: 胡汉三
* @date: 2020/5/26 10:16
* @description: 进行用户账号的验证==>认证功能
*
*/
public class JwtLoginAuthFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
private ThreadLocal rememberMe = new ThreadLocal<>();
public JwtLoginAuthFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
// 设置该过滤器地址
super.setFilterProcessesUrl("/jwt/login");
}
/**
* description: 登录验证
*
* @param request
* @param response
* @return org.springframework.security.core.Authentication
*/
@Override
public Authentication attemptAuthentication(HttpServletRequest request,
HttpServletResponse response) throws AuthenticationException {
rememberMe.set(Boolean.parseBoolean(request.getParameter("rememberMe")));
return authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(request.getParameter("username"),
request.getParameter("password"), new ArrayList<>())
);
}
/**
* description: 登录验证成功后调用,验证成功后将生成Token,并重定向到用户主页home
* 与AuthenticationSuccessHandler作用相同
*
* @param request
* @param response
* @param chain
* @param authResult
* @return void
*/
@Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
// 查看源代码会发现调用getPrincipal()方法会返回一个实现了`UserDetails`接口的对象,这里是CustomUserDetails
CustomUserDetails user = (CustomUserDetails) authResult.getPrincipal();
System.out.println("CustomUserDetails:" + user.toString());
boolean isRemember = rememberMe.get();
List roles = new ArrayList<>();
Collection extends GrantedAuthority> authorities = user.getAuthorities();
for (GrantedAuthority authority : authorities){
roles.add(authority.getAuthority());
}
System.out.println("roles:"+roles);
String token = JwtTokenUtils.createToken(user.getUsername(), roles,isRemember);
System.out.println("token:"+token);
// 登录成功
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
ActionResult result = new ActionResult<>(ResultCodeEnum.SUCCESS);
result.setMessage("登录成功");
result.setData(token);
response.getWriter().write(JSON.toJSONString(result));
}
/**
* description: 登录验证失败后调用,这里直接Json返回,实际上可以重定向到错误界面等
* 与AuthenticationFailureHandler作用相同
*
* @param request
* @param response
* @param failed
* @return void
*/
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
ActionResult result = new ActionResult<>(ResultCodeEnum.LOGIN_FAIL);
response.getWriter().write(JSON.toJSONString(result));
}
}
import com.hzw.code.common.utils.JwtTokenUtils;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
/**
* @author: 胡汉三
* @date: 2020/5/26 10:20
* @description: 对所有请求进行过滤
* BasicAuthenticationFilter继承于OncePerRequestFilter==》确保在一次请求只通过一次filter,而不需要重复执行。
*/
public class JwtPreAuthFilter extends BasicAuthenticationFilter {
public JwtPreAuthFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
/**
* description: 从request的header部分读取Token
*
* @param request
* @param response
* @param chain
* @return void
*/
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain chain) throws IOException, ServletException {
System.out.println("BasicAuthenticationFilters");
String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
System.out.println("tokenHeader:"+tokenHeader);
// 如果请求头中没有Authorization信息则直接放行了
if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
// 如果请求头中有token,则进行解析,并且设置认证信息
SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
super.doFilterInternal(request, response, chain);
}
/**
* description: 读取Token信息,创建UsernamePasswordAuthenticationToken对象
*
* @param tokenHeader
* @return org.springframework.security.authentication.UsernamePasswordAuthenticationToken
*/
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) {
//解析Token时将“Bearer ”前缀去掉
String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
String username = JwtTokenUtils.getUsername(token);
List roles = JwtTokenUtils.getUserRole(token);
Collection authorities = new HashSet<>();
if (roles!=null) {
for (String role : roles) {
authorities.add(new SimpleGrantedAuthority(role));
}
}
if (username != null){
return new UsernamePasswordAuthenticationToken(username, null, authorities);
}
return null;
}
}
import com.alibaba.fastjson.JSON;
import com.hzw.code.common.utils.ActionResult;
import com.hzw.code.common.utils.ResultCodeEnum;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author: 胡汉三
* @date: 2020/5/26 10:44
* @description: 用户登出成功时返回给前端的数据
*/
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
ActionResult result = new ActionResult<>(ResultCodeEnum.SUCCESS);
result.setMessage("登出成功");
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}
import com.alibaba.fastjson.JSON;
import com.hzw.code.common.utils.ActionResult;
import com.hzw.code.common.utils.ResultCodeEnum;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author: 胡汉三
* @date: 2020/5/26 11:16
* @description: 权限不足自定义返回
*
*/
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setCharacterEncoding("UTF-8");
httpServletResponse.setContentType("application/json; charset=utf-8");
ActionResult result = new ActionResult<>(ResultCodeEnum.AUTH_LOWER);
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}
import com.alibaba.fastjson.JSON;
import com.hzw.code.common.utils.ActionResult;
import com.hzw.code.common.utils.ResultCodeEnum;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author: 胡汉三
* @date: 2020/5/26 11:18
* @description: 用户未登录时返回给前端的数据
*/
public class UnAuthorizedEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
ActionResult result = new ActionResult<>(ResultCodeEnum.UNLOGIN);
response.setCharacterEncoding("UTF-8");
response.setContentType("application/json; charset=utf-8");
response.getWriter().write(JSON.toJSONString(result));
}
}
import com.hzw.code.security.filter.JwtLoginAuthFilter;
import com.hzw.code.security.filter.JwtPreAuthFilter;
import com.hzw.code.security.handler.CustomAccessDeniedHandler;
import com.hzw.code.security.handler.CustomLogoutSuccessHandler;
import com.hzw.code.security.handler.UnAuthorizedEntryPoint;
import com.hzw.code.security.service.CustomUserDetailsService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.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.access.AccessDeniedHandler;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
/**
* spring security 配置
*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true,jsr250Enabled = true)
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
private final CustomUserDetailsService userDetailsService;
public SecurityConfiguration(CustomUserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 设置自定义的userDetailsService
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
/**
* description: http细节
*
* @param http
* @return void
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 开启跨域资源共享
http.cors()
.and()
// 关闭csrf
.csrf().disable()
// 关闭session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.httpBasic().authenticationEntryPoint(new UnAuthorizedEntryPoint())
.and()
.formLogin()
.loginPage("/login")
//.successHandler(new Fx)
.and()
.logout()//默认注销行为为logout
.logoutSuccessHandler(new CustomLogoutSuccessHandler())
.and()
// 添加到过滤链中
// 先是UsernamePasswordAuthenticationFilter用于login校验
.addFilter(new JwtLoginAuthFilter(authenticationManager()))
// 再通过OncePerRequestFilter,对其他请求过滤
.addFilter(new JwtPreAuthFilter(authenticationManager()))
// 自定义权限不足返回
.exceptionHandling().accessDeniedHandler(accessDeniedHandler());
}
@Bean
public AccessDeniedHandler accessDeniedHandler() {
return new CustomAccessDeniedHandler();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
}
下面我们来测试一下,看看能不能正常登录
我们之前在数据库中设置的权限就是ROLE_ADMIN,那么说明我们的修改成功了。
----------------------------------------------------------
项目的源码地址:https://gitee.com/gzsjd/fast
----------------------------------------------------------