jwt+springsecurity,springsercurity一定要jwt
00-1010抽象项目使用框架SpringSecurityJWTJWT实例组成介绍JWT认证和授权原理Hutool项目使用表描述SpringSecurity和JWT的集成在pom.xml中添加项目依赖项添加JWT令牌的工具类添加SpringSecurity的配置类相关依赖项和方法添加RestfulAccessDeniedHandler添加RestAuthenticationEntryPoint添加AdminUserDetails添加JwtAuthenticationTokenFilter
00-1010本文主要说明了商城通过集成SpringSecurity和JWT实现后台用户的登录和授权功能,同时改造了Swagger-UI的配置,使其能够自动记忆登录令牌并发送。
目录
摘要
SpringSecurity是一个强大的、高度可定制的认证和授权框架,它是一套针对Spring应用的Web安全标准。SpringSecurity专注于为Java应用提供认证和授权功能。像所有的Spring项目一样,它对定制需求有很强的扩展性。
00-1010JWT是JSON WEB TOKEN的缩写。它是一个基于RFC 7519标准可以安全传输的JSON对象。由于数字签名,它是可信和安全的。
00-1010JWTToken格式:
报头.有效载荷.签名
在报头中存储签名的生成算法
{alg 3360 hs512}负载用于存储用户名、令牌生成时间和到期时间。
{sub 3360 admin , created 3360148907981393, exp 33601489684781}签名是报头和有效载荷生成的签名,一旦报头和有效载荷被篡改,验证将失败。
//secret是加密算法string signature=hmacsha 512(base 64 urlencode(header) . 的密钥base64urlencode(有效负载),机密)
00-1010这是一串JWT
eyjhbgcioijiuzuxmij 9 . eyjzdwioijhzg 1 pbiisimnyzwf 0 zwqioje 1 nty 3 nzk xmjuzmdksimv 4 CCI 6 MTU 1 nzm 4 mzkynx 0 . d-iki 0193 x 0 bboetf 2 un 3r 3 potnieav 7 mzi xxe i5 ixfyzzkozxs 0 pgff _ sk 6 wxc v2 k 8s 0 czjmkv 6 b 5 BC QC 0 vbw
你可以在这个网站上得到分析结果:jwt.io/
00-1010用户调用登录接口,获取令牌;成功登录后的JWT;之后用户每次调用接口,都会在http的头上加一个名为Authorization的头,值为tokenJWT的;后台程序通过解码授权头中的信息并验证数字签名来获取用户信息,从而实现认证和授权。
项目使用框架介绍
Hutool是一个丰富的Java开源工具包,帮助我们简化每一行代码,减少每一个方法。商城项目已经采用了这个工具包。
00-1010 ums _ admin:后台用户表ums_role:后台用户角色表
Ums_permission:后台用户权限表
Ums_admin_role_relation:后台用户和角色关系表,是用户和角色之间的多对多关系。
Ums_role_permission_relation:后台用户角色和权限的关系表,是多对多的关系。
Ums_admin_permission_relation:后台用户和权限表(除了角色中定义的权限,增加权限意味着用户比角色拥有更多的权限,减少权限。
限是指用户比角色少的权限
整合SpringSecurity及JWT
在pom.xml中添加项目依赖
<!--SpringSecurity依赖配置--><dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId></dependency><!--Hutool Java工具包--><dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>4.5.7</version></dependency><!--JWT(Json Web Token)登录支持--><dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId> <version>0.9.0</version></dependency>
添加JWT token的工具类
用于生成和解析JWT token的工具类
相关方法说明:
generateToken(UserDetails userDetails) :用于根据登录用户信息生成token
getUserNameFromToken(String token):从token中获取登录用户的信息
validateToken(String token, UserDetails userDetails):判断token是否还有效
package com.macro.mall.tiny.common.utils;import io.jsonwebtoken.Claims;import io.jsonwebtoken.Jwts;import io.jsonwebtoken.SignatureAlgorithm;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Value;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.stereotype.Component;import java.util.Date;import java.util.HashMap;import java.util.Map;/** * JwtToken生成的工具类 * Created by macro on 2018/4/26. */@Componentpublic class JwtTokenUtil { private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class); private static final String CLAIM_KEY_USERNAME = "sub"; private static final String CLAIM_KEY_CREATED = "created"; @Value("${jwt.secret}") private String secret; @Value("${jwt.expiration}") private Long expiration; /** * 根据负责生成JWT的token */ private String generateToken(Map<String, Object> claims) { return Jwts.builder() .setClaims(claims) .setExpiration(generateExpirationDate()) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } /** * 从token中获取JWT中的负载 */ private Claims getClaimsFromToken(String token) { Claims claims = null; try { claims = Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); } catch (Exception e) { LOGGER.info("JWT格式验证失败:{}",token); } return claims; } /** * 生成token的过期时间 */ private Date generateExpirationDate() { return new Date(System.currentTimeMillis() + expiration * 1000); } /** * 从token中获取登录用户名 */ public String getUserNameFromToken(String token) { String username; try { Claims claims = getClaimsFromToken(token); username = claims.getSubject(); } catch (Exception e) { username = null; } return username; } /** * 验证token是否还有效 * * @param token 客户端传入的token * @param userDetails 从数据库中查询出来的用户信息 */ public boolean validateToken(String token, UserDetails userDetails) { String username = getUserNameFromToken(token); return username.equals(userDetails.getUsername()) && !isTokenExpired(token); } /** * 判断token是否已经失效 */ private boolean isTokenExpired(String token) { Date expiredDate = getExpiredDateFromToken(token); return expiredDate.before(new Date()); } /** * 从token中获取过期时间 */ private Date getExpiredDateFromToken(String token) { Claims claims = getClaimsFromToken(token); return claims.getExpiration(); } /** * 根据用户信息生成token */ public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername()); claims.put(CLAIM_KEY_CREATED, new Date()); return generateToken(claims); } /** * 判断token是否可以被刷新 */ public boolean canRefresh(String token) { return !isTokenExpired(token); } /** * 刷新token */ public String refreshToken(String token) { Claims claims = getClaimsFromToken(token); claims.put(CLAIM_KEY_CREATED, new Date()); return generateToken(claims); }}
添加SpringSecurity的配置类
package com.macro.mall.tiny.config;import com.macro.mall.tiny.component.JwtAuthenticationTokenFilter;import com.macro.mall.tiny.component.RestAuthenticationEntryPoint;import com.macro.mall.tiny.component.RestfulAccessDeniedHandler;import com.macro.mall.tiny.dto.AdminUserDetails;import com.macro.mall.tiny.mbg.model.UmsAdmin;import com.macro.mall.tiny.mbg.model.UmsPermission;import com.macro.mall.tiny.service.UmsAdminService;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.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.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;import org.springframework.security.crypto.password.PasswordEncoder;import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;import java.util.List;/** * SpringSecurity的配置 * Created by macro on 2018/4/26. */@Configuration@EnableWebSecurity@EnableGlobalMethodSecurity(prePostEnabled=true)public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UmsAdminService adminService; @Autowired private RestfulAccessDeniedHandler restfulAccessDeniedHandler; @Autowired private RestAuthenticationEntryPoint restAuthenticationEntryPoint; @Override protected void configure(HttpSecurity httpSecurity) throws Exception { httpSecurity.csrf()// 由于使用的是JWT,我们这里不需要csrf .disable() .sessionManagement()// 基于token,所以不需要session .sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeRequests() .antMatchers(HttpMethod.GET, // 允许对于网站静态资源的无授权访问 "/", "/*.html", "/favicon.ico", "/**/*.html", "/**/*.css", "/**/*.js", "/swagger-resources/**", "/v2/api-docs/**" ) .permitAll() .antMatchers("/admin/login", "/admin/register")// 对登录注册要允许匿名访问 .permitAll() .antMatchers(HttpMethod.OPTIONS)//跨域请求会先进行一次options请求 .permitAll()// .antMatchers("/**")//测试时全部运行访问// .permitAll() .anyRequest()// 除上面外的所有请求全部需要鉴权认证 .authenticated(); // 禁用缓存 httpSecurity.headers().cacheControl(); // 添加JWT filter httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class); //添加自定义未授权和未登录结果返回 httpSecurity.exceptionHandling() .accessDeniedHandler(restfulAccessDeniedHandler) .authenticationEntryPoint(restAuthenticationEntryPoint); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService()) .passwordEncoder(passwordEncoder()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public UserDetailsService userDetailsService() { //获取登录用户信息 return username -> { UmsAdmin admin = adminService.getAdminByUsername(username); if (admin != null) { List<UmsPermission> permissionList = adminService.getPermissionList(admin.getId()); return new AdminUserDetails(admin,permissionList); } throw new UsernameNotFoundException("用户名或密码错误"); }; } @Bean public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){ return new JwtAuthenticationTokenFilter(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); }}
相关依赖及方法说明
configure(HttpSecurity httpSecurity):用于配置需要拦截的url路径、jwt过滤器及出异常后的处理器;
configure(AuthenticationManagerBuilder auth):用于配置UserDetailsService及PasswordEncoder;
RestfulAccessDeniedHandler:当用户没有访问权限时的处理器,用于返回JSON格式的处理结果;
RestAuthenticationEntryPoint:当未登录或token失效时,返回JSON格式的结果;
UserDetailsService:SpringSecurity定义的核心接口,用于根据用户名获取用户信息,需要自行实现;
UserDetails:SpringSecurity定义用于封装用户信息的类(主要是用户信息和权限),需要自行实现;
PasswordEncoder:SpringSecurity定义的用于对密码进行编码及比对的接口,目前使用的是BCryptPasswordEncoder;
JwtAuthenticationTokenFilter:在用户名和密码校验前添加的过滤器,如果有jwt的token,会自行根据token信息进行登录。
添加RestfulAccessDeniedHandler
package com.macro.mall.tiny.component;import cn.hutool.json.JSONUtil;import com.macro.mall.tiny.common.api.CommonResult;import org.springframework.security.access.AccessDeniedException;import org.springframework.security.web.access.AccessDeniedHandler;import org.springframework.stereotype.Component;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;/** * 当访问接口没有权限时,自定义的返回结果 * Created by macro on 2018/4/26. */@Componentpublic class RestfulAccessDeniedHandler implements AccessDeniedHandler{ @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); response.getWriter().println(JSONUtil.parse(CommonResult.forbidden(e.getMessage()))); response.getWriter().flush(); }}
添加RestAuthenticationEntryPoint
package com.macro.mall.tiny.component;import cn.hutool.json.JSONUtil;import com.macro.mall.tiny.common.api.CommonResult;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;/** * 当未登录或者token失效访问接口时,自定义的返回结果 * Created by macro on 2018/5/14. */@Componentpublic class RestAuthenticationEntryPoint implements AuthenticationEntryPoint { @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { response.setCharacterEncoding("UTF-8"); response.setContentType("application/json"); response.getWriter().println(JSONUtil.parse(CommonResult.unauthorized(authException.getMessage()))); response.getWriter().flush(); }}
添加AdminUserDetails
package com.macro.mall.tiny.dto;import com.macro.mall.tiny.mbg.model.UmsAdmin;import com.macro.mall.tiny.mbg.model.UmsPermission;import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;import java.util.Collection;import java.util.List;import java.util.stream.Collectors;/** * SpringSecurity需要的用户详情 * Created by macro on 2018/4/26. */public class AdminUserDetails implements UserDetails { private UmsAdmin umsAdmin; private List<UmsPermission> permissionList; public AdminUserDetails(UmsAdmin umsAdmin, List<UmsPermission> permissionList) { this.umsAdmin = umsAdmin; this.permissionList = permissionList; } @Override public Collection<? extends GrantedAuthority> getAuthorities() { //返回当前用户的权限 return permissionList.stream() .filter(permission -> permission.getValue()!=null) .map(permission ->new SimpleGrantedAuthority(permission.getValue())) .collect(Collectors.toList()); } @Override public String getPassword() { return umsAdmin.getPassword(); } @Override public String getUsername() { return umsAdmin.getUsername(); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return umsAdmin.getStatus().equals(1); }}
添加JwtAuthenticationTokenFilter
在用户名和密码校验前添加的过滤器,如果请求中有jwt的token且有效,会取出token中的用户名,然后调用SpringSecurity的API进行登录操作。
package com.macro.mall.tiny.component;import com.macro.mall.tiny.common.utils.JwtTokenUtil;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.beans.factory.annotation.Value;import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;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;/** * JWT登录授权过滤器 * Created by macro on 2018/4/26. */public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class); @Autowired private UserDetailsService userDetailsService; @Autowired private JwtTokenUtil jwtTokenUtil; @Value("${jwt.tokenHeader}") private String tokenHeader; @Value("${jwt.tokenHead}") private String tokenHead; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { String authHeader = request.getHeader(this.tokenHeader); if (authHeader != null && authHeader.startsWith(this.tokenHead)) { String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer " String username = jwtTokenUtil.getUserNameFromToken(authToken); LOGGER.info("checking username:{}", username); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtTokenUtil.validateToken(authToken, userDetails)) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities()); authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); LOGGER.info("authenticated user:{}", username); SecurityContextHolder.getContext().setAuthentication(authentication); } } } chain.doFilter(request, response); }}
项目源码地址github.com/macrozheng/…
mall持续更新地址:https://github.com/macrozheng/mall
以上就是mall整合SpringSecurity及JWT实现认证授权实战的详细内容,更多关于mall整合SpringSecurity JWT的资料请关注盛行IT其它相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。