Spring Security整合JWT实现前后端分离认证和权限管理(超详细)

阅读: 评论:0

Spring Security整合JWT实现前后端分离认证和权限管理(超详细)

Spring Security整合JWT实现前后端分离认证和权限管理(超详细)

核心步骤

  • 创建项目
  • 配置l
  • 在配置文件中写入jwt相关配置,并创建JWT的配置类,使用@ConfigurationProperties(prefix = “jwt”)与配置文件关联起来
  • 创建自己的用户类
  • 创建自己的无凭证处理类
  • 创建自己的认证失败类
  • 创建自己的权限不足类
  • 创建自己的认证成功处理类
  • 创建自己的UserDetailsService
  • 创建JWT工具类
  • 创建自定义的Token过滤器
  • 创建自己的Spring Secrity配置类(将之前的自定义的配置全部设置进去)

一、创建项目

默认创建Spring Boot项目即可

项目目录:

二、配置

		 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-configuration-processor</artifactId><optional>true</optional></dependency><!-- 在自定义的处理类里面需要使用 --><dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>1.2.73</version></dependency><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-tomcat</artifactId><scope>provided</scope></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- jwt 相关依赖--><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.0</version></dependency>

三、配置JWT相关设置

配置这个主要是为了方便更改jwt相关的一些配置属性,比如加密的时候使用的盐值,token的过期时间等等,我们可以使用@ConfigurationProperties(prefix = "jwt")将配置文件与类联系起来,方便在开发过程中使用

  • 配置文件
#请求头
jwt.header=Authorization
#盐值
jwt.base64-secret=meng
#过期时间
ken-validity-in-seconds=14400000
  • 对应实体类
@Data
@ToString
@Configuration
@ConfigurationProperties(prefix = "jwt") //与配置文件中的数据关联起来(这个注解会自动匹配jwt开头的配置)
public class JwtProperties {/** Request Headers : Authorization */private String header;/** Base64对该令牌进行编码 */private String base64Secret;/** 令牌过期时间 此处单位/毫秒 */private Long tokenValidityInSeconds;
}

四、创建自己的用户类

最好实现UserDetails接口,可以方面我们后面的使用,当然也可以不实现,但是在一些地方需要返回UserDeatils类型的数据,你得再自己做一次转换,很麻烦
注意: 在实现UserDetails接口后,会让你实现下面的一堆方法,你要看清每一个方法都是返回什么信息的,然后对它进行更改,因为你刚刚实现这些方法时,它返回的要么是null,要么是false,下面这个是我改过的。

@Data
public class JwtUser implements UserDetails {   //实现UserDeails接口//用户名private String username;//密码private String password;// 权限(角色)列表Collection<? extends GrantedAuthority> authorities;public JwtUser(String stuId, String password, List<GrantedAuthority> grantedAuthorities) {this.username = stuId;this.password = password;this.authorities = grantedAuthorities;}@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return this.authorities;}@Overridepublic String getUsername() {return this.username;}@Overridepublic boolean isAccountNonExpired() {return true;}@Overridepublic boolean isAccountNonLocked() {return true;}@Overridepublic boolean isCredentialsNonExpired() {return true;}@Overridepublic boolean isEnabled() {return true;}
}

五、自定义无凭证处理类及认证失败处理类

  • 无凭证处理类

当用户没有携带有效凭证时,就会转到这里来,当然,我们还需要在Spring Security的配置类中指定我们自定义的处理类才可以

/*** 认证失败处理类*/
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {@Overridepublic void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException {System.out.println("无凭证");Result r = new Result();r.code(ResultCode.UNAUTHORIZED).message("无凭证");// 使用fastjsonString json =  JSONString(r);// 指定响应格式是jsonresponse.setContentType("text/json;charset=utf-8");Writer().write(json);}
}
  • 自定义认证失败类

当用户输入错误的账号或者密码时,就会进入这个处理类,同样要在配置类中指明(这个类上面的图片中没有,因为我第一个版本没写,这个类应当放到security包下)

/*** 自定义认证失败处理类*/
@Component
public class LoginFailureHandler implements AuthenticationFailureHandler {@Overridepublic void onAuthenticationFailure (HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {returnFailure(httpServletResponse);}public void returnFailure(HttpServletResponse response) throws IOException{Result r = new Result();r.code(ResultCode.UNAUTHORIZED).message("认证失败");// 使用fastjsonString json =  JSONString(r);// 指定响应格式是jsonresponse.setContentType("text/json;charset=utf-8");Writer().write(json);}
}

六、创建自定义权限不足处理类

同样需要在配置类中添加

/*** 自定义无权访问处理类*/
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {@Overridepublic void handle(HttpServletRequest httpServletRequest, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {Result r = new Result();r.code(ResultCode.FORBIDDEN).message("权限不足");String json =  JSONString(r);response.setContentType("text/json;charset=utf-8");Writer().write(json);}
}

七、JWT工具类

这个我是直接复制的别人的,经过自己稍微的修改用起来的,核心的功能就那么几个,只要能与自己的功能对应上就可以,比如我这个刚拿过来的时候它的好多配置都是写死的,而我的一些配置都在配置文件中,那就只需要找到相应位置,改成自己的就行

@Component
public class JwtTokenUtil {// 注入自己的jwt配置@Resourceprivate JwtProperties jwtProperties;static final String CLAIM_KEY_USERNAME = "sub";static final String CLAIM_KEY_AUDIENCE = "audience";static final String CLAIM_KEY_CREATED = "created";private static final String AUDIENCE_UNKNOWN = "unknown";private static final String AUDIENCE_WEB = "web";private static final String AUDIENCE_MOBILE = "mobile";private static final String AUDIENCE_TABLET = "tablet";public String getUsernameFromToken(String token) {String username;try {final Claims claims = getClaimsFromToken(token);username = Subject();} catch (Exception e) {username = null;}return username;}public Date getCreatedDateFromToken(String token) {Date created;try {final Claims claims = getClaimsFromToken(token);created = new Date((Long) (CLAIM_KEY_CREATED));} catch (Exception e) {created = null;}return created;}public Date getExpirationDateFromToken(String token) {Date expiration;try {final Claims claims = getClaimsFromToken(token);//得到token的有效期expiration = Expiration();} catch (Exception e) {expiration = null;}return expiration;}public String getAudienceFromToken(String token) {String audience;try {final Claims claims = getClaimsFromToken(token);audience = (String) (CLAIM_KEY_AUDIENCE);} catch (Exception e) {audience = null;}return audience;}private Claims getClaimsFromToken(String token) {Claims claims;try {claims = Jwts.parser().Base64Secret()).parseClaimsJws(token).getBody();} catch (Exception e) {claims = null;}return claims;}//设置过期时间private Date generateExpirationDate() {return new Date(System.currentTimeMillis() + TokenValidityInSeconds());
//        return new Date(30 * 24 * 60);}private Boolean isTokenExpired(String token) {final Date expiration = getExpirationDateFromToken(token);return expiration.before(new Date());}private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {return (lastPasswordReset != null && created.before(lastPasswordReset));}// Device用户检测当前用户的设备,用不到的话可以删掉(使用这个需要添加相应的依赖)
//    private String generateAudience(Device device) {
//        String audience = AUDIENCE_UNKNOWN;
//        if (device.isNormal()) {
//            audience = AUDIENCE_WEB;
//        } else if (device.isTablet()) {
//            audience = AUDIENCE_TABLET;
//        } else if (device.isMobile()) {
//            audience = AUDIENCE_MOBILE;
//        }
//        return audience;
//    }private Boolean ignoreTokenExpiration(String token) {String audience = getAudienceFromToken(token);return (AUDIENCE_TABLET.equals(audience) || AUDIENCE_MOBILE.equals(audience));}public String generateToken(String username) {Map<String, Object> claims = new HashMap<>();claims.put(CLAIM_KEY_USERNAME, username);claims.put(CLAIM_KEY_CREATED, new Date());return generateToken(claims);}/*** 生成token(最关键)* @param claims* @return*/String generateToken(Map<String, Object> claims) {return Jwts.builder().setClaims(claims)  //设置声明信息(用户名等).setExpiration(generateExpirationDate()) //设置过期时间.signWith(SignatureAlgorithm.HS512, Base64Secret()) //设置签名pact();}public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {final Date created = getCreatedDateFromToken(token);return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)&& (!isTokenExpired(token) || ignoreTokenExpiration(token));}public String refreshToken(String token) {String refreshedToken;try {final Claims claims = getClaimsFromToken(token);claims.put(CLAIM_KEY_CREATED, new Date());refreshedToken = generateToken(claims);} catch (Exception e) {refreshedToken = null;}return refreshedToken;}//TODO,验证当前的token是否有效public Boolean validateToken(String token, UserDetails userDetails) {JwtUser user = (JwtUser) userDetails;final String username = getUsernameFromToken(token);final Date created = getCreatedDateFromToken(token);return (username.Username())&& !isTokenExpired(token));}
}

八、自定义认证成功处理类(关键)

当用户认证成功之后,我们要在这里为用户生成token,并返回给用户,需要用到我们自定义的jwt工具类,也需要在配置类中配置

/*** 自定义认证成功处理器*/@Component
public class JwtAuthenticationSuccessHandler implements AuthenticationSuccessHandler {@Resourceprivate JwtTokenUtil jwtTokenUtil;@Overridepublic void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {//生成tokenfinal String realToken = Name());HashMap<String,Object> map = new HashMap<>();map.put("token", realToken);Result r = new Result();r.code(ResultCode.SUCCESS).message("登录成功").data(map);//将生成的authentication放入容器中,生成安全的上下文Context().setAuthentication(authentication);String json =  JSONString(r);httpServletResponse.setContentType("text/json;charset=utf-8");Writer().write(json);}

九、自定义UserDeailsService

在这里我们要实现用户信息的查询,将查询到的信息返回给Spring Security,让它进行信息的对比,在比对过后会跳转到相应的处理类

这里应该是要到数据库中去查询,我这里暂时写成固定的了

@Service
public class JwtUserDetailServiceImpl implements UserDetailsService {@Overridepublic UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {//暂时写成固定的if(!s.equals("admin")) return null;//用户不是admin,报错System.out.println("查询"+s);return new JwtUser("admin","$2a$10$WtN/GePyTMg3bi3GZeRogowB4ZuoL1zrK", AuthorityUtilsmaSeparatedStringToAuthorityList("user"));}
}

通常都是通过AuthorityUtilsmaSeparatedStringToAuthorityList(“”)来创建authorities集合对象的。参数是一个字符串,多个权限使用逗号分隔。

  • 角色授权:授权代码需要加ROLE_前缀,controller上使用时不要加前缀
  • 权限授权:设置和使用时,名称保持一致即可
  • 数据库查询版:
@Service
@Transactional
public class JwtUserDetailServiceImpl implements UserDetailsService {@Resourceprivate UserMapper userMapper;@Overridepublic UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {QueryWrapper<Admin> wrapper = new QueryWrapper<>();wrapper.eq("username",userName);Admin admin = this.userMapper.selectOne(wrapper);if (admin == null){throw new UsernameNotFoundException("用户名不存在");}return admin;}
}

十、创建自定义的Token过滤器

这个过滤器的主要作用是为了在用户登录并获取到我们发配的token之后,在带着token发送请求时,我们要检验token,判断它是否携带着token,token是否过期,token中的用户是否包含在我们的数据库中等等,如果token有效,则直接让Spring Security形成安全上下文,不再进行验证


@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {@Resourceprivate UserDetailsService userDetailsService;@Resourceprivate JwtTokenUtil jwtTokenUtil;@Resourceprivate JwtProperties jwtProperties;@Overrideprotected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {如果在前端测试时出现跨域问题,到收藏的博客里面看一看String requestUrl = RequestURI();String authToken = Header());String stuId = UsernameFromToken(authToken);System.out.println("进入自定义过滤器");System.out.println("自定义过滤器获得用户名为   "+stuId);//当token中的username不为空时进行验证token是否是有效的tokenif (stuId != null && Context().getAuthentication() == null) {//token中username不为空,并且Context中的认证为空,进行token验证//TODO,从数据库得到带有密码的完整user信息UserDetails userDetails = this.userDetailsService.loadUserByUsername(stuId);if (jwtTokenUtil.validateToken(authToken, userDetails)) { //如username不为空,并且能够在数据库中查到/*** UsernamePasswordAuthenticationToken继承AbstractAuthenticationToken实现Authentication* 所以当在页面中输入用户名和密码之后首先会进入到UsernamePasswordAuthenticationToken验证(Authentication),* 然后生成的Authentication会被交由AuthenticationManager来进行管理* 而AuthenticationManager管理一系列的AuthenticationProvider,* 而每一个Provider都会通UserDetailsService和UserDetail来返回一个* 以UsernamePasswordAuthenticationToken实现的带用户名和密码以及权限的Authentication*/UsernamePasswordAuthenticationToken authentication =new UsernamePasswordAuthenticationToken(userDetails, null, Authorities());authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpServletRequest));//将authentication放入SecurityContextHolder中Context().setAuthentication(authentication);}}filterChain.doFilter(httpServletRequest, httpServletResponse);}
}

十一、Spring Security配置类

这个配置类里面我们要之前的自定义配置全部加进去,并且对路由什么的进行配置

/*** @ClassName: WebSecurityConfig* @Description: TODO Spring Security 配置类* @Author 孟祥龙* @Date: 2021/4/13 8:52* @Version 1.0*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;@Resourceprivate JwtAccessDeniedHandler jwtAccessDeniedHandler;@Resourceprivate JwtAuthenticationSuccessHandler jwtAuthenticationSuccessHandler;@Resourceprivate LoginFailureHandler loginFailureHandler;@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 自定义的Jwt Token过滤器@Beanpublic JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {return new JwtAuthenticationTokenFilter();}@Override@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure(HttpSecurity httpSecurity) throws Exception {httpSecurity.formLogin()//自定义认证成功处理器.successHandler(jwtAuthenticationSuccessHandler)// 自定义失败拦截器.failureHandler(loginFailureHandler)// 自定义登录拦截URI.loginProcessingUrl("/login").and()//token的验证方式不需要开启csrf的防护.csrf().disable()// 自定义认证失败类.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint)// 自定义权限不足处理类.accessDeniedHandler(jwtAccessDeniedHandler).and()//设置无状态的连接,即不创建session.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll().antMatchers("/login").permitAll()//配置允许匿名访问的路径.anyRequest().authenticated();// 解决跨域问题(重要)  只有在前端请求接口时才发现需要这个s().and().csrf().disable();//配置自己的jwt验证过滤器httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);// disable page cachinghttpSecurity.headers().cacheControl();}
}

关于@EnableGlobalMethodSecurity

注意:Spring Security 默认的加密方式就是BCrypt,如果想要详细了解请自行百度

十二、控制器

@RestController
public class AuthController {@RequestMapping("/get")public Result get(){HashMap map = new HashMap();map.put("username","admin");map.put("password","123456");Result r = new Result();r.code(ResultCode.SUCCESS).message("成功访问").data(map);return r;}@PreAuthorize("hasAuthority('admin')")@RequestMapping("/del")public String del(){return "删除成功";}}

十三、测试

  • 登录测试

  • 权限测试


源码地址:
注意: 此代码并不完全正确,应该是缺少了一部分代码的,请对比观看。

本文发布于:2024-03-13 11:27:51,感谢您对本站的认可!

本文链接:https://www.4u4v.net/it/1710733997146897.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:后端   权限   详细   Spring   Security
留言与评论(共有 0 条评论)
   
验证码:

Copyright ©2019-2022 Comsenz Inc.Powered by ©

网站地图1 网站地图2 网站地图3 网站地图4 网站地图5 网站地图6 网站地图7 网站地图8 网站地图9 网站地图10 网站地图11 网站地图12 网站地图13 网站地图14 网站地图15 网站地图16 网站地图17 网站地图18 网站地图19 网站地图20 网站地图21 网站地图22/a> 网站地图23