SpringSecurity+JWT实现前后端分离的使用详解
创建一个配置类SecurityConfig继承WebSecurityConfigurerAdapter
packagetop.ryzeyang.demo.common.config;
importorg.springframework.context.annotation.Bean;
importorg.springframework.security.access.hierarchicalroles.RoleHierarchy;
importorg.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
importorg.springframework.security.authentication.AuthenticationManager;
importorg.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
importorg.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
importorg.springframework.security.config.annotation.web.builders.HttpSecurity;
importorg.springframework.security.config.annotation.web.builders.WebSecurity;
importorg.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
importorg.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
importorg.springframework.security.config.http.SessionCreationPolicy;
importorg.springframework.security.core.userdetails.User;
importorg.springframework.security.core.userdetails.UserDetails;
importorg.springframework.security.core.userdetails.UserDetailsService;
importorg.springframework.security.crypto.factory.PasswordEncoderFactories;
importorg.springframework.security.crypto.password.PasswordEncoder;
importorg.springframework.security.provisioning.InMemoryUserDetailsManager;
importorg.springframework.security.web.AuthenticationEntryPoint;
importorg.springframework.security.web.access.AccessDeniedHandler;
importorg.springframework.security.web.authentication.AuthenticationFailureHandler;
importorg.springframework.security.web.authentication.AuthenticationSuccessHandler;
importorg.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
importorg.springframework.security.web.authentication.logout.LogoutSuccessHandler;
importtop.ryzeyang.demo.common.filter.JwtAuthenticationTokenFilter;
importtop.ryzeyang.demo.utils.JwtTokenUtil;
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
publicclassSecurityConfigextendsWebSecurityConfigurerAdapter{
finalAuthenticationFailureHandlerauthenticationFailureHandler;
finalAuthenticationSuccessHandlerauthenticationSuccessHandler;
finalAuthenticationEntryPointauthenticationEntryPoint;
finalAccessDeniedHandleraccessDeniedHandler;
finalLogoutSuccessHandlerlogoutSuccessHandler;
publicSecurityConfig(AuthenticationFailureHandlerauthenticationFailureHandler,AuthenticationSuccessHandlerauthenticationSuccessHandler,AuthenticationEntryPointauthenticationEntryPoint,AccessDeniedHandleraccessDeniedHandler,LogoutSuccessHandlerlogoutSuccessHandler){
this.authenticationFailureHandler=authenticationFailureHandler;
this.authenticationSuccessHandler=authenticationSuccessHandler;
this.authenticationEntryPoint=authenticationEntryPoint;
this.accessDeniedHandler=accessDeniedHandler;
this.logoutSuccessHandler=logoutSuccessHandler;
}
@Bean
publicPasswordEncoderpasswordEncoder(){
returnPasswordEncoderFactories.createDelegatingPasswordEncoder();
}
@Bean("users")
publicUserDetailsServiceusers(){
UserDetailsuser=User.builder()
.username("user")
.password("{bcrypt}$2a$10$1.NSMxlOyMgJHxOi8CWwxuU83G0/HItXxRoBO4QWZMTDp0tzPbCf.")
.roles("USER")
//roles和authorities不能并存
//.authorities(newString[]{"system:user:query","system:user:edit","system:user:delete"})
.build();
UserDetailsadmin=User.builder()
.username("admin")
.password("{bcrypt}$2a$10$1.NSMxlOyMgJHxOi8CWwxuU83G0/HItXxRoBO4QWZMTDp0tzPbCf.")
//.roles("USER","ADMIN")
.roles("ADMIN")
//.authorities("system:user:create")
.build();
returnnewInMemoryUserDetailsManager(user,admin);
}
/**
*角色继承:
*让Admin角色拥有User的角色的权限
*@return
*/
@Bean
publicRoleHierarchyroleHierarchy(){
RoleHierarchyImplresult=newRoleHierarchyImpl();
result.setHierarchy("ROLE_ADMIN>ROLE_USER");
returnresult;
}
@Override
protectedvoidconfigure(AuthenticationManagerBuilderauth)throwsException{
auth.userDetailsService(users());
}
@Override
publicvoidconfigure(WebSecurityweb)throwsException{
web.ignoring().antMatchers("/js/**","/css/**","/images/**");
}
@Override
protectedvoidconfigure(HttpSecurityhttp)throwsException{
//自定义异常处理
http.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.accessDeniedHandler(accessDeniedHandler)
//权限
.and()
.authorizeRequests()
//一个是必须待身份信息但是不校验权限。
.antMatchers("/","/mock/login").permitAll()
//只允许匿名访问
.antMatchers("/hello").anonymous()
.anyRequest()
.authenticated()
//表单登录
//.and()
//.formLogin()
//.successHandler(authenticationSuccessHandler)
//.failureHandler(authenticationFailureHandler)
//.loginProcessingUrl("/login")
//.permitAll()
//注销
.and()
.logout()
.logoutUrl("/logout")
.logoutSuccessHandler(logoutSuccessHandler)
.permitAll()
//关闭csrf会在页面中生成一个csrf_token
.and()
.csrf()
.disable()
//基于token,所以不需要session
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//添加我们的JWT过滤器
.and()
.addFilterBefore(jwtAuthenticationTokenFilter(),UsernamePasswordAuthenticationFilter.class)
;
}
@Bean
publicJwtAuthenticationTokenFilterjwtAuthenticationTokenFilter(){
returnnewJwtAuthenticationTokenFilter();
}
@Bean
@Override
publicAuthenticationManagerauthenticationManagerBean()throwsException{
returnsuper.authenticationManagerBean();
}
@Bean
publicJwtTokenUtiljwtTokenUtil(){
returnnewJwtTokenUtil();
}
}
创建JWT过滤器继承OncePerRequestFilter
这里直接用的macro大佬mall商城里的例子
packagetop.ryzeyang.demo.common.filter;
importlombok.extern.slf4j.Slf4j;
importorg.slf4j.Logger;
importorg.slf4j.LoggerFactory;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.beans.factory.annotation.Qualifier;
importorg.springframework.beans.factory.annotation.Value;
importorg.springframework.security.authentication.UsernamePasswordAuthenticationToken;
importorg.springframework.security.core.context.SecurityContextHolder;
importorg.springframework.security.core.userdetails.UserDetails;
importorg.springframework.security.core.userdetails.UserDetailsService;
importorg.springframework.security.web.authentication.WebAuthenticationDetailsSource;
importorg.springframework.stereotype.Component;
importorg.springframework.web.filter.OncePerRequestFilter;
importtop.ryzeyang.demo.utils.JwtTokenUtil;
importjavax.servlet.FilterChain;
importjavax.servlet.ServletException;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importjava.io.IOException;
/**
*JWT登录授权过滤器
*
*@authormacro
*@date2018/4/26
*/
@Slf4j
publicclassJwtAuthenticationTokenFilterextendsOncePerRequestFilter{
@Qualifier("users")
@Autowired
privateUserDetailsServiceuserDetailsService;
@Autowired
privateJwtTokenUtiljwtTokenUtil;
@Value("${jwt.tokenHeader}")
privateStringtokenHeader;
@Value("${jwt.tokenHead}")
privateStringtokenHead;
@Override
protectedvoiddoFilterInternal(HttpServletRequestrequest,
HttpServletResponseresponse,
FilterChainchain)throwsServletException,IOException{
StringauthHeader=request.getHeader(this.tokenHeader);
if(authHeader!=null&&authHeader.startsWith(this.tokenHead)){
StringauthToken=authHeader.substring(this.tokenHead.length());//Thepartafter"Bearer"
Stringusername=jwtTokenUtil.getUserNameFromToken(authToken);
log.info("checkingusername:{}",username);
if(username!=null&&SecurityContextHolder.getContext().getAuthentication()==null){
UserDetailsuserDetails=this.userDetailsService.loadUserByUsername(username);
if(jwtTokenUtil.validateToken(authToken,userDetails)){
UsernamePasswordAuthenticationTokenauthentication=newUsernamePasswordAuthenticationToken(userDetails,null,userDetails.getAuthorities());
authentication.setDetails(newWebAuthenticationDetailsSource().buildDetails(request));
log.info("authenticateduser:{}",username);
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
}
chain.doFilter(request,response);
}
}
自定义handler
这里主要介绍两个hanler,一个权限不足,一个验证失败的
权限不足实现AccessDeniedHandler
packagetop.ryzeyang.demo.common.handler;
importcom.fasterxml.jackson.databind.ObjectMapper;
importorg.springframework.security.access.AccessDeniedException;
importorg.springframework.security.web.access.AccessDeniedHandler;
importorg.springframework.stereotype.Component;
importtop.ryzeyang.demo.common.api.CommonResult;
importtop.ryzeyang.demo.common.api.ResultEnum;
importjavax.servlet.ServletException;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importjava.io.IOException;
importjava.io.PrintWriter;
@Component
publicclassMyAccessDeineHandlerimplementsAccessDeniedHandler{
@Override
publicvoidhandle(HttpServletRequesthttpServletRequest,HttpServletResponsehttpServletResponse,AccessDeniedExceptione)throwsIOException,ServletException{
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriterwriter=httpServletResponse.getWriter();
writer.write(newObjectMapper().writeValueAsString(newCommonResult<>(ResultEnum.ACCESS_ERROR,e.getMessage())));
writer.flush();
writer.close();
}
}
认证失败实现AuthenticationEntryPoint
如账号密码错误等验证不通过时
packagetop.ryzeyang.demo.common.handler;
importcom.fasterxml.jackson.databind.ObjectMapper;
importorg.springframework.security.core.AuthenticationException;
importorg.springframework.security.web.AuthenticationEntryPoint;
importorg.springframework.stereotype.Component;
importtop.ryzeyang.demo.common.api.CommonResult;
importtop.ryzeyang.demo.common.api.ResultEnum;
importjavax.servlet.ServletException;
importjavax.servlet.http.HttpServletRequest;
importjavax.servlet.http.HttpServletResponse;
importjava.io.IOException;
importjava.io.PrintWriter;
@Component
publicclassMyAuthenticationEntryPointimplementsAuthenticationEntryPoint{
@Override
publicvoidcommence(HttpServletRequesthttpServletRequest,HttpServletResponsehttpServletResponse,AuthenticationExceptione)throwsIOException,ServletException{
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriterwriter=httpServletResponse.getWriter();
//认证失败
writer.write(newObjectMapper().writeValueAsString(newCommonResult<>(ResultEnum.AUTHENTICATION_ERROR,e.getMessage())));
writer.flush();
writer.close();
}
}
JWT工具类
这里直接用的macro大佬mall商城里的例子
稍微改了一点,因为JDK11用的jjwt版本不一样,语法也有些不同
pom文件中引入jjwt
io.jsonwebtoken jjwt-api 0.11.2 io.jsonwebtoken jjwt-impl 0.11.2 runtime io.jsonwebtoken jjwt-jackson 0.11.2 runtime
JwtTokenUtil
packagetop.ryzeyang.demo.utils;
importcn.hutool.core.date.DateUtil;
importcn.hutool.core.util.StrUtil;
importio.jsonwebtoken.Claims;
importio.jsonwebtoken.Jwts;
importio.jsonwebtoken.io.Decoders;
importio.jsonwebtoken.security.Keys;
importlombok.extern.slf4j.Slf4j;
importorg.springframework.beans.factory.annotation.Value;
importorg.springframework.security.core.userdetails.UserDetails;
importjavax.crypto.SecretKey;
importjava.util.Date;
importjava.util.HashMap;
importjava.util.Map;
/**
*JwtToken生成的工具类
*JWTtoken的格式:header.payload.signature
*header的格式(算法、token的类型):
*{"alg":"HS512","typ":"JWT"}
*payload的格式(用户名、创建时间、生成时间):
*{"sub":"wang","created":1489079981393,"exp":1489684781}
*signature的生成算法:
*HMACSHA512(base64UrlEncode(header)+"."+base64UrlEncode(payload),secret)
*
*@authormacro
*@date2018/4/26
*/
@Slf4j
publicclassJwtTokenUtil{
privatestaticfinalStringCLAIM_KEY_USERNAME="sub";
privatestaticfinalStringCLAIM_KEY_CREATED="created";
@Value("${jwt.secret}")
privateStringsecret;
@Value("${jwt.expiration}")
privateLongexpiration;
@Value("${jwt.tokenHead}")
privateStringtokenHead;
privateSecretKeygetSecretKey(){
byte[]encodeKey=Decoders.BASE64.decode(secret);
returnKeys.hmacShaKeyFor(encodeKey);
}
/**
*根据负责生成JWT的token
*/
privateStringgenerateToken(Mapclaims){
SecretKeysecretKey=getSecretKey();
returnJwts.builder()
.setClaims(claims)
.setExpiration(generateExpirationDate())
.signWith(secretKey)
.compact();
}
/**
*测试生成的token
*@paramclaims
*@return
*/
publicStringgenerateToken2(Mapclaims){
SecretKeysecretKey=getSecretKey();
returnJwts.builder()
.setClaims(claims)
.setIssuer("Java4ye")
.setExpiration(newDate(System.currentTimeMillis()+1*1000))
.signWith(secretKey)
.compact();
}
/**
*从token中获取JWT中的负载
*/
privateClaimsgetClaimsFromToken(Stringtoken){
SecretKeysecretKey=getSecretKey();
Claimsclaims=null;
try{
claims=Jwts.parserBuilder()
.setSigningKey(secretKey)
.build()
.parseClaimsJws(token)
.getBody();
}catch(Exceptione){
log.info("JWT格式验证失败:{}",token);
}
returnclaims;
}
/**
*生成token的过期时间
*/
privateDategenerateExpirationDate(){
returnnewDate(System.currentTimeMillis()+expiration*1000);
}
/**
*从token中获取登录用户名
*/
publicStringgetUserNameFromToken(Stringtoken){
Stringusername;
try{
Claimsclaims=getClaimsFromToken(token);
username=claims.getSubject();
}catch(Exceptione){
username=null;
}
returnusername;
}
/**
*验证token是否还有效
*
*@paramtoken客户端传入的token
*@paramuserDetails从数据库中查询出来的用户信息
*/
publicbooleanvalidateToken(Stringtoken,UserDetailsuserDetails){
Stringusername=getUserNameFromToken(token);
returnusername.equals(userDetails.getUsername())&&!isTokenExpired(token);
}
/**
*判断token是否已经失效
*/
privatebooleanisTokenExpired(Stringtoken){
DateexpiredDate=getExpiredDateFromToken(token);
returnexpiredDate.before(newDate());
}
/**
*从token中获取过期时间
*/
privateDategetExpiredDateFromToken(Stringtoken){
Claimsclaims=getClaimsFromToken(token);
returnclaims.getExpiration();
}
/**
*根据用户信息生成token
*/
publicStringgenerateToken(UserDetailsuserDetails){
Mapclaims=newHashMap<>();
claims.put(CLAIM_KEY_USERNAME,userDetails.getUsername());
claims.put(CLAIM_KEY_CREATED,newDate());
returngenerateToken(claims);
}
/**
*当原来的token没过期时是可以刷新的
*
*@paramoldToken带tokenHead的token
*/
publicStringrefreshHeadToken(StringoldToken){
if(StrUtil.isEmpty(oldToken)){
returnnull;
}
Stringtoken=oldToken.substring(tokenHead.length());
if(StrUtil.isEmpty(token)){
returnnull;
}
//token校验不通过
Claimsclaims=getClaimsFromToken(token);
if(claims==null){
returnnull;
}
//如果token已经过期,不支持刷新
if(isTokenExpired(token)){
returnnull;
}
//如果token在30分钟之内刚刷新过,返回原token
if(tokenRefreshJustBefore(token,30*60)){
returntoken;
}else{
claims.put(CLAIM_KEY_CREATED,newDate());
returngenerateToken(claims);
}
}
/**
*判断token在指定时间内是否刚刚刷新过
*
*@paramtoken原token
*@paramtime指定时间(秒)
*/
privatebooleantokenRefreshJustBefore(Stringtoken,inttime){
Claimsclaims=getClaimsFromToken(token);
Datecreated=claims.get(CLAIM_KEY_CREATED,Date.class);
DaterefreshDate=newDate();
//刷新时间在创建时间的指定时间内
if(refreshDate.after(created)&&refreshDate.before(DateUtil.offsetSecond(created,time))){
returntrue;
}
returnfalse;
}
}
配置文件application.yml
这里的secret可以用该方法生成
@Test
voidgenerateKey(){
/**
*SECRET是签名密钥,只生成一次即可,生成方法:
*Keykey=Keys.secretKeyFor(SignatureAlgorithm.HS256);
*StringsecretString=Encoders.BASE64.encode(key.getEncoded());#本文使用BASE64编码
**/
Keykey=Keys.secretKeyFor(SignatureAlgorithm.HS512);
StringsecretString=Encoders.BASE64.encode(key.getEncoded());
System.out.println(secretString);
//Blk1X8JlN4XH4s+Kuc0YUFXv+feyTgVUMycSiKbiL0YhRddy872mCNZBGZIb57Jn2V1RtaFXIxs8TvNPsnG//g==
}
jwt: tokenHeader:Authorization secret:Blk1X8JlN4XH4s+Kuc0YUFXv+feyTgVUMycSiKbiL0YhRddy872mCNZBGZIb57Jn2V1RtaFXIxs8TvNPsnG//g== expiration:604800 tokenHead:'Bearer' server: servlet: context-path:/api
Controller
AuthController
packagetop.ryzeyang.demo.controller;
importorg.springframework.beans.factory.annotation.Autowired;
importorg.springframework.beans.factory.annotation.Qualifier;
importorg.springframework.security.authentication.AuthenticationManager;
importorg.springframework.security.authentication.UsernamePasswordAuthenticationToken;
importorg.springframework.security.core.Authentication;
importorg.springframework.security.core.GrantedAuthority;
importorg.springframework.security.core.context.SecurityContextHolder;
importorg.springframework.security.core.userdetails.UserDetails;
importorg.springframework.security.core.userdetails.UserDetailsService;
importorg.springframework.web.bind.annotation.*;
importtop.ryzeyang.demo.common.api.CommonResult;
importtop.ryzeyang.demo.model.dto.UserDTO;
importtop.ryzeyang.demo.utils.CommonResultUtil;
importtop.ryzeyang.demo.utils.JwtTokenUtil;
importjava.util.Collection;
@RestController
@ResponseBody
@RequestMapping("/mock")
publicclassAuthController{
@Autowired
privateJwtTokenUtiljwtTokenUtil;
@Qualifier("users")
@Autowired
privateUserDetailsServiceuserDetailsService;
@Autowired
AuthenticationManagerauthenticationManager;
@GetMapping("/userinfo")
publicCommonResult>getUserInfo(){
Authenticationauthentication=SecurityContextHolder.getContext().getAuthentication();
Collectionauthorities=authentication.getAuthorities();
returnCommonResultUtil.success(authorities);
}
/**
*模拟登陆
*/
@PostMapping("/login")
publicCommonResultlogin(@RequestBodyUserDTOuserDTO){
Stringusername=userDTO.getUsername();
Stringpassword=userDTO.getPassword();
UsernamePasswordAuthenticationTokentoken
=newUsernamePasswordAuthenticationToken(username,password);
Authenticationauthenticate=authenticationManager.authenticate(token);
SecurityContextHolder.getContext().setAuthentication(authenticate);
UserDetailsuserDetails=userDetailsService.loadUserByUsername(username);
Stringt=jwtTokenUtil.generateToken(userDetails);
returnCommonResultUtil.success(t);
}
}
HelloController
packagetop.ryzeyang.demo.controller;
importorg.springframework.security.access.prepost.PreAuthorize;
importorg.springframework.web.bind.annotation.GetMapping;
importorg.springframework.web.bind.annotation.RestController;
@RestController
publicclassHelloController{
@GetMapping("/hello")
publicStringhello(){
return"hello";
}
@GetMapping("/hello/anonymous")
publicStringhello2(){
return"anonymous";
}
@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/hello/admin")
publicStringhelloAdmin(){
return"helloAdmin";
}
@PreAuthorize("hasRole('USER')")
@GetMapping("/hello/user")
publicStringhelloUser(){
return"hellouser";
}
@PreAuthorize("hasAnyAuthority('system:user:query')")
@GetMapping("/hello/user2")
publicStringhelloUser2(){
return"hellouser2";
}
}
项目地址在GitHub上
地址:SpringSecurity-Vuetify-Permissions-demo
到此这篇关于SpringSecurity+JWT实现前后端分离的使用详解的文章就介绍到这了,更多相关SpringSecurity+JWT前后端分离内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!