热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

springSecurity前后端分离集成jwt(4)

一前言大家好,我是知识追寻者,本篇内容是springSecurity第四篇;没有相关基础的同学请学习后再来看这篇内容;文末附源码地址;

一 前言

大家好,我是知识追寻者,本篇内容是springSecurity第四篇;没有相关基础的同学请学习后再来看这篇内容;文末附源码地址;

二 pom

pom 文件引入的依赖 , security 的启动器支持security 功能;lombok 进行简化开发; fastjson 进行Json处理;

jjwt 进行jwt token 支持;lang3 字符串处理;



org.springframework.boot
spring-boot-starter-web


org.springframework.boot
spring-boot-starter-security


org.projectlombok
lombok
1.16.18
provided



com.alibaba
fastjson
1.2.62


io.jsonwebtoken
jjwt
0.9.0


org.apache.commons
commons-lang3
3.4

三 认证流程
  • SecurityContextHolder,提供SecurityContext的访问权限。
  • SecurityContext,保存Authentication和可能的特定于请求的安全信息。
  • Authentication,以特定于Spring Security的方式代表校验。
  • GrantedAuthority,以反映授予主体的应用程序范围的权限。
  • UserDetails,提供从应用程序的DAO或其他安全数据源构建Authentication对象所需的信息。
  • UserDetailsService,在基于String的用户名(或证书ID等)中传递时创建UserDetails

上面的意思不难理解, 从数据源中获取 用户信息 组装到 UserDetails, 然后通过UserDetailsService,传递 UserDetails; SecurityContextHolder 存储 整个 用户上下文信息,通过SecurityContext 存储 Authentication, 这样就保证了 springSecurity 持有用户信息;

四 实体

SysUser 实现 UserDetails 用于储存用户信息, 主要是用户名,密码, 和权限;

/**
* @Author lsc
*


*/
@Data
public class SysUser implements UserDetails {
// 用户名
private String username;
// 密码
private String password;
// 权限信息
private Set authorities;
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

五 token工具类

token 工具类主要用于生产 token, 解析token, 校验token;这边需要注意的是,将 权限 归并到了生成 toekn 的步骤,这样通过 token就可以获取 权限,在权限校验时通过token就可以获取权限信息;缺点就进行授权的之后的token应为未更新会造成权限未同步;

/**
* @Author lsc
*


*/
public class JwtUtil {
private static final String CLAIMS_ROLE = "zszxzRoles";
/**
* 5天(毫秒)
*/
private static final long EXPIRATION_TIME = 1000 * 60 * 60 * 5;
/**
* JWT密码
*/
private static final String SECRET = "secret";
/**
* 签发JWT
*/
public static String getToken(String username, String roles) {
Map claims = new HashMap<>(8);
// 主体
claims.put( CLAIMS_ROLE, roles);
return Jwts.builder()
.setClaims(claims)
.claim("username",username)
.setExpiration( new Date( Instant.now().toEpochMilli() + EXPIRATION_TIME ) )// 过期时间
.signWith( SignatureAlgorithm.HS512, SECRET )// 加密
.compact();
}
/**
* 验证JWT
*/
public static Boolean validateToken(String token) {
return (!isTokenExpired( token ));
}
/**
* 获取token是否过期
*/
public static Boolean isTokenExpired(String token) {
Date expiration = getExpireTime( token );
return expiration.before( new Date() );
}
/**
* 根据token获取username
*/
public static String getUsernameByToken(String token) {
String username = (String) parseToken( token ).get("username");
return username;
}
public static Set getRolseByToken(String token) {
String rolse = (String) parseToken( token ).get(CLAIMS_ROLE);
String[] strArray = StringUtils.strip(rolse, "[]").split(", ");
Set authoritiesSet = new HashSet();
if (strArray.length>0){
Arrays.stream(strArray).forEach(rols-> {
GrantedAuthority authority = new SimpleGrantedAuthority(rols);
authoritiesSet.add(authority);
});
}
return authoritiesSet;
}
/**
* 获取token的过期时间
*/
public static Date getExpireTime(String token) {
Date expiration = parseToken( token ).getExpiration();
return expiration;
}
/**
* 解析JWT
*/
private static Claims parseToken(String token) {
Claims claims = Jwts.parser()
.setSigningKey( SECRET )
.parseClaimsJws( token )
.getBody();
return claims;
}
}

六 UserDetailsService

UserDetailsService 用户查询数据库的数据信息,进行用户数据封装到UserDetails, 在进行用户身份认证的时候会走这边; 这边采用官方提供的PasswordEncoder 进行加密; 其配置方式需要在WebSecurityConfig 中 配置;

/**
* @Author lsc
*


*/
@Component
@Slf4j
public class SysUserDetailsService implements UserDetailsService {
@Autowired
private PasswordEncoder passwordEncoder;
// 登陆验证时,通过username获取用户的所有权限信息; 正式环境中就是查询用户数据授权
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
log.info("------用户{}身份认证-----",username);
// 新建用户
SysUser user = new SysUser();
// 账号
user.setUsername(username);
// 密码
user.setPassword(passwordEncoder.encode("123456"));
// 设置权限
Set authoritiesSet = new HashSet();
// 注意角色权限需要加 ROLE_前缀,否则报403
GrantedAuthority userPower = new SimpleGrantedAuthority("ROLE_USER");
GrantedAuthority adminPower = new SimpleGrantedAuthority("ROLE_ADMIN");
authoritiesSet.add(userPower);
authoritiesSet.add(adminPower);
user.setAuthorities(authoritiesSet);
return user;
}
}

七 JWTLoginFilter

JWTLoginFilter 继承 AbstractAuthenticationProcessingFilter 过滤器;理论上继承 UsernamePasswordAuthenticationFilter 也是 可行,毕竟 UsernamePasswordAuthenticationFilter 是 AbstractAuthenticationProcessingFilter 的实现类;

JWTLoginFilter 用于用户登陆认证,其实现如下 三个方法 ;

  • attemptAuthentication 用于 尝试认证,如果认证成功会走 successfulAuthentication 方法;如果认证失败会走 unsuccessfulAuthentication 方法;
  • successfulAuthentication 认证成功后我们需要生成一个token,返回以JSON的形式返回给前端;
  • unsuccessfulAuthentication 认证失败,我们通过异常信息判定,然后返回错误信息给前端;

/**
* @Author lsc
*

登陆认证过滤器


*/
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter {
public JWTLoginFilter(String defaultFilterProcessesUrl, AuthenticationManager authenticationManager) {
super(new AntPathRequestMatcher(defaultFilterProcessesUrl));
setAuthenticationManager(authenticationManager);
}
/**
* @Author lsc
*

登陆认证


* @Param [request, response]
* @Return
*/
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException {
SysUser user = new ObjectMapper().readValue(request.getInputStream(), SysUser.class);
UsernamePasswordAuthenticationToken authenticatiOnToken= new UsernamePasswordAuthenticationToken(
user.getUsername(),
user.getPassword());
return getAuthenticationManager().authenticate(authenticationToken);
}
/**
* @Author lsc
*

登陆成功返回token


* @Param [request, res, chain, auth]
* @Return
*/
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,FilterChain chain, Authentication auth){
SysUser principal = (SysUser)auth.getPrincipal();
String token = JwtUtil.getToken(principal.getUsername(),principal.getAuthorities().toString());
try {
//登录成功時,返回json格式进行提示
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter out = response.getWriter();
ResultPage result = ResultPage.sucess(CodeMsg.SUCESS,token);
out.write(new ObjectMapper().writeValueAsString(result));
out.flush();
out.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
@Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
String result="";
// 账号过期
if (failed instanceof AccountExpiredException) {
result="账号过期";
}
// 密码错误
else if (failed instanceof BadCredentialsException) {
result="密码错误";
}
// 密码过期
else if (failed instanceof CredentialsExpiredException) {
result="密码过期";
}
// 账号不可用
else if (failed instanceof DisabledException) {
result="账号不可用";
}
//账号锁定
else if (failed instanceof LockedException) {
result="账号锁定";
}
// 用户不存在
else if (failed instanceof InternalAuthenticationServiceException) {
result="用户不存在";
}
// 其他错误
else{
result="未知异常";
}
// 处理编码方式 防止中文乱码
response.setContentType("text/json;charset=utf-8");
// 将反馈塞到HttpServletResponse中返回给前台
response.getWriter().write(JSON.toJSONString(result));
}
}

八 WebSecurityConfig

WebSecurityConfig 是 springSecurity 的配置相关信息;在配置中,可以进行数据访问权限限制,授权异常处理,账号加密方式等配置;

/**
* @Author lsc
*


*/
@EnableWebSecurity// 开启springSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
DenyHandler denyHandler;
@Autowired
OutSuccessHandler outSuccessHandler;
@Autowired
SysUserDetailsService userDetailsService;
@Autowired
ExpAuthenticationEntryPoint expAuthenticationEntryPoint;
/* *
* @Author lsc
*

授权


* @Param [http]
*/
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()// 授权
.antMatchers("/api/download/**").anonymous()// 匿名用户权限
.antMatchers("/api/**").hasRole("USER")//普通用户权限
.antMatchers("/api/admin/**").hasRole("ADMIN")// 管理员权限
.antMatchers("/login").permitAll()
//其他的需要授权后访问
.anyRequest().authenticated()
.and()// 异常
.exceptionHandling()
.accessDeniedHandler(denyHandler)//授权异常处理
.authenticationEntryPoint(expAuthenticationEntryPoint)// 认证异常处理
.and()
.logout()
.logoutSuccessHandler(outSuccessHandler)
.and()
.addFilterBefore(new JWTLoginFilter("/login",authenticationManager()),UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()),UsernamePasswordAuthenticationFilter.class)
.sessionManagement()
// 设置Session的创建策略为:Spring Security不创建HttpSession
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable();// 关闭 csrf 否则post
}
/* *
* @Author lsc
*

认证 设置加密方式


* @Param [auth]
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
@Bean
@Override
public AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
}

九 Handler

配置中使用到了3个处理类,分别是 denyHandler, outSuccessHandler, expAuthenticationEntryPoint;

其中 denyHandler 当权限进行校验时,如果权限不足就会走这个处理类

/**
* @Author lsc
*

权限不足处理


*/
@Component
public class DenyHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
// 设置响应头
httpServletResponse.setContentType("application/json;charset=utf-8");
// 返回值
ResultPage result = ResultPage.error(CodeMsg.PERM_ERROR);
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}

outSuccessHandler 是退出登陆处理类,默认地址 localhost:8080/logout;

/**
* @Author lsc
*


*/
@Component
public class OutSuccessHandler implements LogoutSuccessHandler {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
// 设置响应头
httpServletResponse.setContentType("application/json;charset=utf-8");
// 返回值
ResultPage result = ResultPage.sucess(CodeMsg.SUCESS,"退出登陆成功");
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}

expAuthenticationEntryPoint 负责身份认证通过后异常处理,每个主要身份验证系统都有自己的AuthenticationEntryPoint实现;

/**
* @Author lsc
*


*/
@Component
public class ExpAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
// 设置响应头
httpServletResponse.setContentType("application/json;charset=utf-8");
// 返回值
ResultPage result = ResultPage.error(CodeMsg.ACCOUNT_ERROR);
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}

十 Controller

SysUserController 用于 提供权限测试

/**
* @Author lsc
*


*/
@RestController
public class SysUserController {
@GetMapping("api/admin")
@PreAuthorize("hasAuthority('ADMIN')")
public String authAdmin() {
return "需要ADMIN权限";
}
@GetMapping("api/test")
@PreAuthorize("hasAuthority('USER')")
public String authUser() {
return "需要USER权限";
}
}

整体项目结构如下

十一 测试

用户登陆 ,返回token

请求接口测试,返回数据

用户退出返回信息;

最后

参考文档

https://blog.csdn.net/Piconjo/article/details/106156383

https://www.jianshu.com/p/8bd4a6e27e7f

https://www.jianshu.com/p/bd882078fac4

https://docs.spring.io/spring-security/site/docs/5.3.3.BUILD-SNAPSHOT/reference/html5/

源码地址:公众号后台回复: springsecurity


推荐阅读
  • 本文介绍了Windows Vista操作系统中的用户账户保护功能,该功能是为了增强系统的安全性而设计的。通过对Vista测试版的体验,可以看到系统在安全性方面的进步。该功能的引入,为用户的账户安全提供了更好的保障。 ... [详细]
  • Spring源码解密之默认标签的解析方式分析
    本文分析了Spring源码解密中默认标签的解析方式。通过对命名空间的判断,区分默认命名空间和自定义命名空间,并采用不同的解析方式。其中,bean标签的解析最为复杂和重要。 ... [详细]
  • Iamtryingtomakeaclassthatwillreadatextfileofnamesintoanarray,thenreturnthatarra ... [详细]
  • 向QTextEdit拖放文件的方法及实现步骤
    本文介绍了在使用QTextEdit时如何实现拖放文件的功能,包括相关的方法和实现步骤。通过重写dragEnterEvent和dropEvent函数,并结合QMimeData和QUrl等类,可以轻松实现向QTextEdit拖放文件的功能。详细的代码实现和说明可以参考本文提供的示例代码。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
  • 在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板
    本文介绍了在Xamarin XAML语言中如何在页面级别构建ControlTemplate控件模板的方法和步骤,包括将ResourceDictionary添加到页面中以及在ResourceDictionary中实现模板的构建。通过本文的阅读,读者可以了解到在Xamarin XAML语言中构建控件模板的具体操作步骤和语法形式。 ... [详细]
  • 图像因存在错误而无法显示 ... [详细]
  • Activiti7流程定义开发笔记
    本文介绍了Activiti7流程定义的开发笔记,包括流程定义的概念、使用activiti-explorer和activiti-eclipse-designer进行建模的方式,以及生成流程图的方法。还介绍了流程定义部署的概念和步骤,包括将bpmn和png文件添加部署到activiti数据库中的方法,以及使用ZIP包进行部署的方式。同时还提到了activiti.cfg.xml文件的作用。 ... [详细]
  • 有没有一种方法可以在不继承UIAlertController的子类或不涉及UIAlertActions的情况下 ... [详细]
  • 如何在服务器主机上实现文件共享的方法和工具
    本文介绍了在服务器主机上实现文件共享的方法和工具,包括Linux主机和Windows主机的文件传输方式,Web运维和FTP/SFTP客户端运维两种方式,以及使用WinSCP工具将文件上传至Linux云服务器的操作方法。此外,还介绍了在迁移过程中需要安装迁移Agent并输入目的端服务器所在华为云的AK/SK,以及主机迁移服务会收集的源端服务器信息。 ... [详细]
  • 本文介绍了南邮ctf-web的writeup,包括签到题和md5 collision。在CTF比赛和渗透测试中,可以通过查看源代码、代码注释、页面隐藏元素、超链接和HTTP响应头部来寻找flag或提示信息。利用PHP弱类型,可以发现md5('QNKCDZO')='0e830400451993494058024219903391'和md5('240610708')='0e462097431906509019562988736854'。 ... [详细]
  • Android日历提醒软件开源项目分享及使用教程
    本文介绍了一款名为Android日历提醒软件的开源项目,作者分享了该项目的代码和使用教程,并提供了GitHub项目地址。文章详细介绍了该软件的主界面风格、日程信息的分类查看功能,以及添加日程提醒和查看详情的界面。同时,作者还提醒了读者在使用过程中可能遇到的Android6.0权限问题,并提供了解决方法。 ... [详细]
  • 大数据Hadoop生态(20)MapReduce框架原理OutputFormat的开发笔记
    本文介绍了大数据Hadoop生态(20)MapReduce框架原理OutputFormat的开发笔记,包括outputFormat接口实现类、自定义outputFormat步骤和案例。案例中将包含nty的日志输出到nty.log文件,其他日志输出到other.log文件。同时提供了一些相关网址供参考。 ... [详细]
author-avatar
mobiledu2502860643
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有