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. sql_update

    学习: http://www.runoob.com/mysql/mysql-tutorial.html replace: update table_name set column = replace ...

  2. 课堂笔记及知识点----UI框架简介(2018/10/25)

    UI框架学习目标: 要知道怎样套用的! 框架里面的基本执行流程 怎样开始执行(配置文件) 怎么套用 最主要的三个脚本: (也是多态的体现之一) 1).BaseUI: 作用-->提供UI能够使用的 ...

  3. LAMP构架搭建论坛

    安装MYSQL数据库服务程序       LAMP动态网站部署架构是一套由Linux+Nginx+MYSQL+PHP组成的动态网站系统解决方案,具有免费.高效.扩展性强且资源消耗低等优良特性.使用手工 ...

  4. 电子科技大学实验中学PK赛(二)比赛题解

    比赛地址:http://qscoj.cn/contest/27/ A题 FIFA强化 分析:这个题目要求说的比较明显,用几个if判断一下就好了.不要一判断完就输出,最好用一个ans储存下答案.输出答案 ...

  5. mybatis generator自动生成代码时 只生成了insert 而没有其他的

    mybatis框架提供了非常好用的逆向工程插件,但是在使用过程中会有很多问题. 我在使用中就遇到了只生成insert和insertSeletive方法,而不生成其他根据primary key查询更新删 ...

  6. Python档案袋(线程 )

    Python的进程和线程是使用的操作系统的原生线程和进程,其是去调用操作系统的相应接口实现 进程:之间不可直接共享数据,是资源的集合,进程必须有一个线程 线程:基于进程,之间可直接共享数据,可执行,只 ...

  7. Hecher学生互助平台(团队项目第一次)

    团队项目作业链接:https://edu.cnblogs.com/campus/xnsy/SoftwareEngineeringClass1/homework/2978 一.团队简介 团队名称:Suc ...

  8. Python内置函数(65)——type

    英文文档: class type(object) class type(name, bases, dict) With one argument, return the type of an obje ...

  9. .NET Core WebApi中实现多态数据绑定

    什么是多态数据绑定? 我们都知道在ASP.NET Core WebApi中数据绑定机制(Data Binding)负责绑定请求参数, 通常情况下大部分的数据绑定都能在默认的数据绑定器(Binder)中 ...

  10. Zend与PHP之间到底是什么关系

    Zend与PHP之间是什么关系 What is Zend's relationship with PHP? 每次看到PHP虚拟机中出现zend.zendvar之类的都很困惑,特意查了一下... PHP ...