1.背景

基于前后端分离项目的后端模块;

2.相关技术

  • springboot全家桶

    • web模块
    • security模块;用于权限的验证
    • mongodb 模块;集成mogodb模块
  • jwt 用于token的生成
  • mongodb
  • lomok
  • 后续会细分出更多的模块。用上springcloud全家桶

3.权限验证流程

3.1 构建User对象

实现security的UserDetail。之后所有权限获取都是从这个对象中返回

重写的默认属性必须返回true,不然在登录那块验证该属性是不是true。如果默认返回false,会报出各种用户相关的异常

@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class JwtUser implements UserDetails { private String username;
private String password;
private Collection<? extends GrantedAuthority> authorities; public JwtUser(String username, String password, Collection<? extends GrantedAuthority> authorities) {
this.username = username;
this.password = password;
this.authorities = authorities;
} @Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return this.authorities;
} @Override
public String getPassword() {
return this.password;
} @Override
public String getUsername() {
return this.username;
} @Override
public boolean isAccountNonExpired() {
return true;
} /**
* Indicates whether the user is locked or unlocked. A locked user cannot be
* authenticated.
*
* @return <code>true</code> if the user is not locked, <code>false</code> otherwise
*/
@Override
public boolean isAccountNonLocked() {
return true;
} /**
* Indicates whether the user's credentials (password) has expired. Expired
* credentials prevent authentication.
*
* @return <code>true</code> if the user's credentials are valid (ie non-expired),
* <code>false</code> if no longer valid (ie expired)
*/
@Override
public boolean isCredentialsNonExpired() {
return true;
} /**
* Indicates whether the user is enabled or disabled. A disabled user cannot be
* authenticated.
*
* @return <code>true</code> if the user is enabled, <code>false</code> otherwise
*/
@Override
public boolean isEnabled() {
return true;
}

3.JwtUserDetailsServiceImpl

重写security的UserDaiService的loadByusername方法,实现自定义的权限验证

@Service
public class JwtUserDetailsServiceImpl implements UserDetailsService { @Autowired
private UserService userService; /**
* Locates the user based on the username. In the actual implementation, the search
* may possibly be case sensitive, or case insensitive depending on how the
* implementation instance is configured. In this case, the <code>UserDetails</code>
* object that comes back may have a username that is of a different case than what
* was actually requested..
*
* @param username the username identifying the user whose data is required.
* @return a fully populated user record (never <code>null</code>)
* @throws UsernameNotFoundException if the user could not be found or the user has no
* GrantedAuthority
*/
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException{
//设置查询条件,邮箱是唯一的
User queryUser = new User();
queryUser.setEmail(username);
List<User> userList = null;
try {
userList = this.userService.getUser(queryUser); if (CollectionUtils.isEmpty(userList)) {
//return new JwtUser(username, queryUser.getPwd(), authorities);
throw new UsernameNotFoundException("用户账号:" + username + ",不存在");
} else {
queryUser = userList.get(0);
Set<GrantedAuthority> authorities = new HashSet<>();
//获取该用户所有的权限信息
this.userService.getRoleByUserId(queryUser.getId()).forEach(role -> {
authorities.add(new SimpleGrantedAuthority(role.getRoleCode()));
}); return new JwtUser(username, queryUser.getPwd(), authorities);
}
} catch (Exception e) {
e.printStackTrace();
} return null;
}
}

3.3 token生成方法

@Component
public class JwtTokenUtil implements Serializable {
/**
* 密钥
*/
private final String secret = "code4fun"; final static Long TIMESTAMP = 86400000L;
final static String TOKEN_PREFIX = "Bearer"; /**
* 从数据声明生成令牌
*
* @param claims 数据声明
* @return 令牌
*/
private String generateToken(Map<String, Object> claims) {
Date expirationDate = new Date(System.currentTimeMillis() + TIMESTAMP);
return TOKEN_PREFIX + " " +Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
} /**
* 从令牌中获取数据声明
*
* @param token 令牌
* @return 数据声明
*/
private Claims getClaimsFromToken(String token) {
Claims claims;
try {
claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
} catch (Exception e) {
claims = null;
}
return claims;
} /**
* 生成令牌
*
* @param userDetails 用户
* @return 令牌
*/
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>(2);
claims.put("sub", userDetails.getUsername());
claims.put("created", new Date());
return generateToken(claims);
} /**
* 从令牌中获取用户名
*
* @param token 令牌
* @return 用户名
*/
public String getUsernameFromToken(String token) {
String username;
try {
Claims claims = getClaimsFromToken(token);
username = claims.getSubject();
} catch (Exception e) {
username = null;
}
return username;
} /**
* 判断令牌是否过期
*
* @param token 令牌
* @return 是否过期
*/
public Boolean isTokenExpired(String token) {
try {
Claims claims = getClaimsFromToken(token);
Date expiration = claims.getExpiration();
return expiration.before(new Date());
} catch (Exception e) {
return false;
}
} /**
* 刷新令牌
*
* @param token 原令牌
* @return 新令牌
*/
public String refreshToken(String token) {
String refreshedToken;
try {
Claims claims = getClaimsFromToken(token);
claims.put("created", new Date());
refreshedToken = generateToken(claims);
} catch (Exception e) {
refreshedToken = null;
}
return refreshedToken;
} /**
* 验证令牌
*
* @param token 令牌
* @param userDetails 用户
* @return 是否有效
*/
public Boolean validateToken(String token, UserDetails userDetails) {
JwtUser user = (JwtUser) userDetails;
String username = getUsernameFromToken(token);
return (username.equals(user.getUsername()) && !isTokenExpired(token));
}
}

3.4 token校验过滤器

每次请求的时候都会被该过滤器过滤拦截。主要是校验token的有效性

@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { @Autowired
private JwtUserDetailsServiceImpl userDetailsService;
private JwtTokenUtil jwtTokenUtil; public JwtAuthenticationTokenFilter(JwtTokenUtil jwtTokenUtil) {
this.jwtTokenUtil = jwtTokenUtil;
} /**
* 每个请求都被拦截
* Same contract as for {@code doFilter}, but guaranteed to be
* just invoked once per request within a single request thread.
* See {@link #shouldNotFilterAsyncDispatch()} for details.
* <p>Provides HttpServletRequest and HttpServletResponse arguments instead of the
* default ServletRequest and ServletResponse ones.
*
* @param request
* @param response
* @param filterChain
*/
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
String tokenHead = "Bearer "; if (authHeader != null && authHeader.startsWith(tokenHead)) { String authToken = authHeader.substring(tokenHead.length());
String username = jwtTokenUtil.getUsernameFromToken(authToken); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
//返回jwtUser
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(authToken, userDetails)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
//将该用户的权限信息存放到threadlocal中
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
} filterChain.doFilter(request, response);
}
}

3.4 webSecurity配置

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private JwtUserDetailsServiceImpl userDetailsService;
@Autowired
private JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;
// private EntryPointUnauthorizedHandler entryPointUnauthorizedHandler;
// private RestAccessDeniedHandler restAccessDeniedHandler; @Autowired
public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
authenticationManagerBuilder.userDetailsService(this.userDetailsService).passwordEncoder(passwordEncoder());
} /**
* 注入密码BCryptPasswordEncoder
* 在添加用户的时候,要用 BCryptPasswordEncoder.encode()加密
* @return
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
} @Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
} @Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests()
.antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
.antMatchers("/user/**", "/login",
"/js/**", "/bootstrap/**", "/css/**", "/images/**", "/fonts/**").permitAll() //静态文件拦截 .anyRequest().authenticated()
.and().headers().cacheControl();
httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class); }
}

至此,相关的配置就配置完了。在登录操作的时候需要注意一下:

用户信息的验证全部交给spring security来操作,代码如下:

    /**
* 登录操作,返回token
* @param userName
* @param password
* @return
* @throws Exception
*/
@Override
public String login(String userName, String password) throws Exception {
UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken(userName, password);
Authentication authentication = authenticationManager.authenticate(upToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = userDetailsService.loadUserByUsername(userName);
return jwtTokenUtil.generateToken(userDetails);
}

3.4 用户验证流程

UsernamePasswordAuthenticationToken
authenticationManager.authenticate(upToken);
//通过这个创建一个代理(ProviderManager)对象
delegate = this.delegateBuilder.getObject();
//调用代理对象的认证方法
delegate.authenticate(authentication)
1.代理对象调用父类的 parent.authenticate(authentication);认证方法
1.进到parent.authenticate方法,去定ProvideManager的具体类型是DaoProviderManager
2.provider.authenticate(authentication); //此时的provider是DaoProviderManager
1.判断参数authentication是不是UsernamePasswordAuthenticationToken类型;不是则跑出异常
2.取出唯一标识字段username
1.判断userCache是否包含user缓存
1.不在缓存中,创建user对象并存放到缓存中
//调用这个方法转换成user对象
1.user = retrieveUser(username,
(UsernamePasswordAuthenticationToken) authentication);
//调用用户自定义实现了UserDetailService的方法来获得user对象
1.UserDetails loadedUser = this.getUserDetailsService().loadUserByUsername(username);
2.preAuthenticationChecks.check(user);
1.preAuthenticationChecks.check校验上一部返回的user对象的属性,只要用户实现的userDetail的get,set方法赋上值就好了
additionalAuthenticationChecks(user,
(UsernamePasswordAuthenticationToken) authentication);
1.uthentication.getCredentials() == null判断密码是不是为空
2.presentedPassword = authentication.getCredentials().toString(); 获取页面传递过来的密码
3.passwordEncoder.matches(presentedPassword, userDetails.getPassword())判断页面上传递过来的密码跟数据库中的密码是不是一致。
1.调用BCrypt.checkpw(rawPassword.toString(), encodedPassword)比对
1.调用 hashpw 来加密页面传递过来的密码信息。然后与数据库中的密码比对。如果相同则返回成功,不同则报错

3.5 开源地址

github地址 欢迎指导

后续将补上验证流程

springboot、springsecurity、jwt权限验证的更多相关文章

  1. Z从壹开始前后端分离【 .NET Core2.2/3.0 +Vue2.0 】框架之五 || Swagger的使用 3.3 JWT权限验证【必看】

    本文梯子 本文3.0版本文章 前言 1.如何给接口实现权限验证? 零.生成 Token 令牌 一.JWT ——自定义中间件 0.Swagger中开启JWT服务 1:API接口授权策略 2.自定义认证之 ...

  2. 轻松上手SpringBoot+SpringSecurity+JWT实RESTfulAPI权限控制实战

    前言 我们知道在项目开发中,后台开发权限认证是非常重要的,springboot 中常用熟悉的权限认证框架有,shiro,还有就是springboot 全家桶的 security当然他们各有各的好处,但 ...

  3. 从壹开始前后端分离【 .NET Core2.0 +Vue2.0 】框架之五 || Swagger的使用 3.3 JWT权限验证【必看】

    前言 关于JWT一共三篇 姊妹篇,内容分别从简单到复杂,一定要多看多想: 一.Swagger的使用 3.3 JWT权限验证[修改] 二.解决JWT权限验证过期问题 三.JWT完美实现权限与接口的动态分 ...

  4. .Net Core3.0 WebApi 项目框架搭建 四:JWT权限验证

    .Net Core3.0 WebApi 项目框架搭建:目录 什么是JWT 根据维基百科定义,JWT(读作 [/dʒɒt/]),即JSON Web Tokens,是一种基于JSON的.用于在网络上声明某 ...

  5. SpringBoot+SpringSecurity+jwt整合及初体验

    原来一直使用shiro做安全框架,配置起来相当方便,正好有机会接触下SpringSecurity,学习下这个.顺道结合下jwt,把安全信息管理的问题扔给客户端, 准备 首先用的是SpringBoot, ...

  6. spring boot+jwt 权限验证

    上周看了一下jwt以前公司的开发都是使用session共享的方法.现在主流的两种方式一种是把登录信息保存在服务器,另一种则是把信息保存在客户端.在使用session 存储的时候会遇到很多的问题,随着项 ...

  7. spring-boot-plus集成Shiro+JWT权限管理

    SpringBoot+Shiro+JWT权限管理 Shiro Apache Shiro是一个强大且易用的Java安全框架,执行身份验证.授权.密码和会话管理. 使用Shiro的易于理解的API,您可以 ...

  8. springboot使用jwt进行权限验证

    springboot使用jwt进行权限验证 依赖准备 首先导入对应的依赖 <dependencies> <dependency> <groupId>org.apac ...

  9. SpringBoot + SpringSecurity + Quartz + Layui实现系统权限控制和定时任务

    1. 简介   Spring Security是一个功能强大且易于扩展的安全框架,主要用于为Java程序提供用户认证(Authentication)和用户授权(Authorization)功能.    ...

随机推荐

  1. redis离线集群安装

    用一个叫redis-trib.rb的ruby脚本.redis-trib.rb是redis官方推出的管理redis集群的工具,集成在redis的源码src目录下(redis-xxx/src/).是基于r ...

  2. [.net 面向对象程序设计深入](36)Redis——基础

    [.net 面向对象程序设计深入](36)Redis——基础 很长一段时间没更新博客了,坚持做一件事,真不是件容易的事,后面我会继续尽可能的花时间更新完这个系列文章. 因这个系列的文章涉及的范围太大了 ...

  3. Javascript高级编程学习笔记(88)—— Canvas(5)绘制文本

    绘制文本 同样的,canvas也为绘制文本提供了相应的方法. 2D上下文提供的文本绘制方法主要有两个: fillText() strokeText() 这两个方法都接受四个参数 要绘制的文本字符串 绘 ...

  4. [Swift]LeetCode684. 冗余连接 | Redundant Connection

    In this problem, a tree is an undirected graph that is connected and has no cycles. The given input ...

  5. [Swift]LeetCode781. 森林中的兔子 | Rabbits in Forest

    In a forest, each rabbit has some color. Some subset of rabbits (possibly all of them) tell you how ...

  6. Spark MLlib

    MLlib    数据挖掘与机器学习        数据挖掘体系 数据挖掘:也就是data mining,是一个很宽泛的概念,也是一个新兴学科,旨在如何从海量数据中挖掘出有用的信息来.         ...

  7. webstorm 支持vue element-ui 语法高亮属性自动补全

    如果webstorm中 提示 Unknown html tag el-*** 说明没有加载 node_modules 下的 element-ui 解决办法就是: 在webstorm 打开的状态下 第一 ...

  8. 【Storm篇】--Storm中的同步服务DRPC

    一.前述 Drpc(分布式远程过程调用)是一种同步服务实现的机制,在Storm中客户端提交数据请求之后,立刻取得计算结果并返回给客户端.同时充分利用Storm的计算能力实现高密度的并行实时计算. 二. ...

  9. BBS论坛(十一)

    11.1.前台用户模型创建 (1)apps/front/models.py 首先安装:pip install shortuuid class FrontUser(db.Model): __tablen ...

  10. Java核心技术及面试指南:视频列表

    如下是本书相关内容的视频列表,会动态更新 第一章 1 视频1.1  JDK和JRE和JVM的区别,安装Java开发环境    1.1.1  第2页 2 视频1.2  编写第一个HelloWorld程序 ...