前言
其实挺早就想写一篇关于jwt的博文去好好总结一下之前踩过的坑了,但是事情有点太多了,一直没抽出时间来写,刚好现在有点时间可以好好静下来写一遍(可能)有点质量的博文吧,毕竟一直都是看别人的博文去学习,我也好好写一遍吧哈哈。
看完这篇文章之后你可以知道
如何使用springboot,springSecurity,jwt实现基于token的权限管理
统一处理无权限请求的结果
整理一下思路
创建一个新工程时,我们需要思考一下我们接下来需要的一些步骤,需要做什么,怎么做。
搭建springboot工程
导入springSecurity跟jwt的依赖
用户的实体类
dao层
service层(真正开发时再写,这里就直接调用dao层操作数据库)
实现UserDetailsService接口
实现UserDetails接口
验证用户登录信息的拦截器
验证用户权限的拦截器
springSecurity配置
认证的Controller以及测试的controller
直接上手
源码地址:https://gitee.com/zhu_can_admin/security.git
引入相关依赖
<!-- spring security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- jwt -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!--lombok依赖-->
jwt工具包
public class JwtTokenUtils {
public static final String TOKEN_HEADER = "Authorization";
public static final String TOKEN_PREFIX = "Bearer ";
private static final String SECRET = "jwtsecretdemo";
private static final String ISS = "echisan";
// 角色的key
private static final String ROLE_CLAIMS = "rol";
// 过期时间是3600秒,既是1个小时
private static final long EXPIRATION = 3600L;
// 选择了记住我之后的过期时间为7天
private static final long EXPIRATION_REMEMBER = 604800L;
// 创建token
public static String createToken(String username,String role, boolean isRememberMe) {
long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
HashMap<String, Object> map = new HashMap<>();
map.put(ROLE_CLAIMS, role);
return Jwts.builder()
.signWith(SignatureAlgorithm.HS512, SECRET)
.setClaims(map)
.setIssuer(ISS)
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration * 1000))
.compact();
}
//验证token合法性
public static Boolean verifToken(String token, String username) {
boolean isExpiration = isExpiration(token);
String name = Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody().getSubject();
return (name.equals(username) && !isExpiration);
}
// 从token中获取用户名
public static String getUsername(String token){
return getTokenBody(token).getSubject();
}
// 获取用户角色
public static String getUserRole(String token){
return (String) getTokenBody(token).get(ROLE_CLAIMS);
}
// 是否已过期
public static boolean isExpiration(String token) {
try {
return getTokenBody(token).getExpiration().before(new Date());
} catch (ExpiredJwtException e) {
return true;
}
}
private static Claims getTokenBody(String token){
return Jwts.parser()
.setSigningKey(SECRET)
.parseClaimsJws(token)
.getBody();
}
}
user实体
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements UserDetails {
@ApiModelProperty(value = "")
private Long id;
@ApiModelProperty(value = "")
private String username;
@ApiModelProperty(value = "")
private String idcard;
@ApiModelProperty(value = "")
private String telephon;
@ApiModelProperty(value = "")
private String email;
@ApiModelProperty(value = "")
private String password;
@ApiModelProperty(value = "")
private String role;
private List<GrantedAuthority> authorities;
public void setAuthorities(List<GrantedAuthority> authorities) {
this.authorities = authorities;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
}
@Override
public String getUsername() {
return this.username;
}
/**
* 账户是否未过期,过期无法验证
*/
@Override
public boolean isAccountNonExpired() {
return true;
}
/**
* 指定用户是否解锁,锁定的用户无法进行身份验证
*
* @return
*/
@Override
public boolean isAccountNonLocked() {
return true;
}
/**
* 指示是否已过期的用户的凭据(密码),过期的凭据防止认证
*
* @return
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
}
/**
* 是否可用 ,禁用的用户不能身份验证
*
* @return
*/
@Override
public boolean isEnabled() {
return true;
}
}
使用springSecurity需要实现UserDetailsService接口供权限框架调用查询数据库里面用户的信息
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
User user = userMapper.selectByName(s);
//AuthorityUtils.commaSeparatedStringToAuthorityList将逗号分隔的字符集转成权限对象列表
user.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(user.getRole()));
return user;
}
}
配置拦截器
在spring-security中,这边需要实现两个过滤器。使用JWTAuthenticationFilter去进行用户账号的验证,使用JWTAuthorizationFilter去进行用户权限的验证。
JWTAuthenticationFilter继承于UsernamePasswordAuthenticationFilter
该拦截器用于获取用户登录的信息,只需创建一个token并调用authenticationManager.authenticate()让spring-security去进行验证就可以了,不用自己查数据库再对比密码了,这一步交给spring去操作。
这个操作有点像是shiro的subject.login(new UsernamePasswordToken()),验证的事情交给框架。
献上这一部分的代码。
JWTAuthorizationFilter继承于BasicAuthenticationFilter
该拦截器用于进行权限认证
// 用户账号的验证
public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter {
private AuthenticationManager authenticationManager;
public JWTAuthenticationFilter(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
//设置登陆url
super.setFilterProcessesUrl("/login");
}
//登陆获取登陆信息
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
String username = request.getParameter("username");
String password = request.getParameter("password");
//自动进行密码校验
return authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
}
//验证成功后会进入这个方法
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException {
User user = (User) authResult.getPrincipal();
String token = JwtTokenUtils.createToken(user.getUsername(), user.getRole(), true);
// 返回创建成功的tokenuser
// 但是这里创建的token只是单纯的token
// 按照jwt的规定,最后请求的时候应该是 `Bearer token`
response.setContentType("application/json;charset=utf-8");
response.setHeader("token", JwtTokenUtils.TOKEN_PREFIX + token);
response.getWriter().write("{\"code\":\"200\",\"msg\":\"登录成功\",\"token\":\"" + token + "\"}");
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("登陆失败: " + failed.getMessage());
}
}
//进行权限认证
public class JWTAuthorizationFilter extends OncePerRequestFilter{
@Autowired
private UserDetailsService UserDetailsServiceImpl;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String tokenHeader = request.getHeader(JwtTokenUtils.TOKEN_HEADER);
// 如果请求头中没有Authorization信息则直接放行了
if (tokenHeader == null || !tokenHeader.startsWith(JwtTokenUtils.TOKEN_PREFIX)) {
chain.doFilter(request, response);
return;
}
//设置用户身份授权
try {
SecurityContextHolder.getContext().setAuthentication(getAuthentication(tokenHeader));
} catch (TokenIsExpiredException e) {
throw new RuntimeException("token解析错误");
}
chain.doFilter(request, response);
}
// 这里从token中获取用户信息并新建一个token
private UsernamePasswordAuthenticationToken getAuthentication(String tokenHeader) throws TokenIsExpiredException {
String token = tokenHeader.replace(JwtTokenUtils.TOKEN_PREFIX, "");
User userDetails = (User) UserDetailsServiceImpl.loadUserByUsername(JwtTokenUtils.getUsername(token));
String username = userDetails.getUsername();
String role = userDetails.getRole();
//验证token合法性
if (JwtTokenUtils.verifToken(token, userDetails.getUsername())) {
return new UsernamePasswordAuthenticationToken(username, null,
AuthorityUtils.commaSeparatedStringToAuthorityList(role));
} else {
throw new TokenIsExpiredException("token超时了或者携带的token不合法");
}
}
}
Handler 用来配置没有权限访问和token失效返回的数据
**
*
* @description:没有访问权限
*/
public class JWTAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.getWriter().write("{\"code\":\"403\",\"msg\":\"没有权限\"}");
}
}
/**
* @description:没有携带token或者token无效
*/
public class JWTAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException, ServletException {
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("{\"code\":\"405\",\"msg\":\"token失效或未携带token\"}");
}
}
核心配置SecurityConfig
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// @Autowired
// private MyAuthencationFailureHandler failureHandler;
// @Autowired
// private MyAuthenticationSuccessHandler successHandler;
@Autowired
@Qualifier("userDetailsServiceImpl")
private UserDetailsService userDetailsService;
/**
* 当前配置为form表单登录认证方式
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// 禁用 csrf, 由于使用的是JWT,我们这里不需要csrf
http.cors().and().csrf().disable()
.authorizeRequests()
// 放行URL
.antMatchers("/login").permitAll()
.antMatchers("/swagger**/**").permitAll()
.antMatchers("/webjars/**").permitAll()
.antMatchers("/v2/**").permitAll()
//admin权限才能访问
//对应数据库role为ROLE_user
.antMatchers("/index").hasAnyRole("user")
//对应数据库role为admin
.antMatchers("/system/user").hasAnyAuthority("admin")
// 其他所有请求需要身份认证
.anyRequest().authenticated()
.and()
//用户登录过滤器,校验用户名和密码
.addFilter(new JWTAuthenticationFilter(authenticationManager()))
//用户权限拦截器
.addFilterBefore(jwtAuthorizationFilter, BasicAuthenticationFilter.class)
// 不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
//没有携带token或者token无效拦截器
.exceptionHandling().authenticationEntryPoint(new JWTAuthenticationEntryPoint())
//添加无权限时的处理拦截器
.accessDeniedHandler(new JWTAccessDeniedHandler());
//登出功能
http.logout().logoutUrl("/logout");
}
//登录验证逻辑
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// 使用自定义登录身份认证组件
auth.userDetailsService(userDetailsService).passwordEncoder(getBCryptPasswordEncoder());
}
//跨域
@Bean
CorsConfigurationSource corsConfigurationSource() {
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", new CorsConfiguration().applyPermitDefaultValues());
return source;
}
//强散列哈谢加密实现
@Bean
public BCryptPasswordEncoder getBCryptPasswordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
contoller
@Controller
public class HomeController {
// 登录成功首页
@GetMapping("/index")
public String index(){
return "index";
}
//用户管理
@GetMapping("/system/user")
public String userList(){
return "user";
}
//角色管理
@GetMapping("/system/role")
public String roleList(){
return "role";
}
//菜单管理
@GetMapping("/system/menu")
public String menuList(){
return "menu";
}
//订单管理
@GetMapping("/order")
public String orderList(){
return "order";
}
}