2019独角兽企业重金招聘Python工程师标准>>>


写在前面

  • 关于 Spring Security Web系统的认证和权限模块也算是一个系统的基础设施了,几乎任何的互联网服务都会涉及到这方面的要求。在Java EE领域,成熟的安全框架解决方案一般有 Apache Shiro、Spring Security等两种技术选型。Apache Shiro简单易用也算是一大优势,但其功能还是远不如 Spring Security强大。Spring Security可以为 Spring 应用提供声明式的安全访问控制,起通过提供一系列可以在 Spring应用上下文中可配置的Bean,并利用 Spring IoC和 AOP等功能特性来为应用系统提供声明式的安全访问控制功能,减少了诸多重复工作。

  • 关于JWT JSON Web Token (JWT),是在网络应用间传递信息的一种基于 JSON的开放标准((RFC 7519),用于作为JSON对象在不同系统之间进行安全地信息传输。主要使用场景一般是用来在 身份提供者和服务提供者间传递被认证的用户身份信息。关于JWT的科普,可以看看阮一峰老师的《JSON Web Token 入门教程》。

本文则结合 Spring Security和 JWT两大利器来打造一个简易的权限系统。

本文实验环境如下:

  • Spring Boot版本:2.0.6.RELEASE
  • IDE:IntelliJ IDEA 2018.2.4

另外本文实验代码置于文尾,需要自取。


设计用户和角色

本文实验为了简化考虑,准备做如下设计:

  • 设计一个最简角色表role,包括角色ID角色名称

  • 设计一个最简用户表user,包括用户ID用户名密码

  • 再设计一个用户和角色一对多的关联表user_roles 一个用户可以拥有多个角色

创建 Spring Security和 JWT加持的 Web工程

  • pom.xml 中引入 Spring Security和 JWT所必需的依赖
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</artifactId>
</dependency><dependency><groupId>io.jsonwebtoken</groupId><artifactId>jjwt</artifactId><version>0.9.0</version>
</dependency>
  • 项目配置文件中加入数据库和 JPA等需要的配置
server.port=9991spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://121.196.XXX.XXX:3306/spring_security_jwt?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=XXXXXXlogging.level.org.springframework.security=infospring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jackson.serialization.indent_output=true
  • 创建用户、角色实体

用户实体 User

/*** @ www.codesheep.cn* 20190312*/
@Entity
public class User implements UserDetails {@Id@GeneratedValueprivate Long id;private String username;private String password;@ManyToMany(cascade = {CascadeType.REFRESH},fetch = FetchType.EAGER)private List<Role> roles;...// 下面为实现UserDetails而需要的重写方法!@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {List<GrantedAuthority> authorities = new ArrayList<>();for (Role role : roles) {authorities.add( new SimpleGrantedAuthority( role.getName() ) );}return authorities;}...
}

此处所创建的 User类继承了 Spring Security的 UserDetails接口,从而成为了一个符合 Security安全的用户,即通过继承 UserDetails,即可实现 Security中相关的安全功能。

角色实体 Role:

/*** @ www.codesheep.cn* 20190312*/
@Entity
public class Role {@Id@GeneratedValueprivate Long id;private String name;... // 省略 getter和 setter
}
  • 创建JWT工具类

主要用于对 JWT Token进行各项操作,比如生成Token、验证Token、刷新Token等

/*** @ www.codesheep.cn* 20190312*/
@Component
public class JwtTokenUtil implements Serializable {private static final long serialVersionUID = -5625635588908941275L;private static final String CLAIM_KEY_USERNAME = "sub";private static final String CLAIM_KEY_CREATED = "created";public String generateToken(UserDetails userDetails) {...}String generateToken(Map<String, Object> claims) {...}public String refreshToken(String token) {...}public Boolean validateToken(String token, UserDetails userDetails) {...}... // 省略部分工具函数
}
  • 创建Token过滤器,用于每次外部对接口请求时的Token处理
/*** @ www.codesheep.cn* 20190312*/
@Component
public class JwtTokenFilter extends OncePerRequestFilter {@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Overrideprotected void doFilterInternal ( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {String authHeader = request.getHeader( Const.HEADER_STRING );if (authHeader != null && authHeader.startsWith( Const.TOKEN_PREFIX )) {final String authToken = authHeader.substring( Const.TOKEN_PREFIX.length() );String username = jwtTokenUtil.getUsernameFromToken(authToken);if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);if (jwtTokenUtil.validateToken(authToken, userDetails)) {UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));SecurityContextHolder.getContext().setAuthentication(authentication);}}}chain.doFilter(request, response);}
}
  • Service业务编写

主要包括用户登录和注册两个主要的业务

public interface AuthService {User register( User userToAdd );String login( String username, String password );
}
/*** @ www.codesheep.cn* 20190312*/
@Service
public class AuthServiceImpl implements AuthService {@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate UserDetailsService userDetailsService;@Autowiredprivate JwtTokenUtil jwtTokenUtil;@Autowiredprivate UserRepository userRepository;// 登录@Overridepublic String login( String username, String password ) {UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken( username, password );final Authentication authentication = authenticationManager.authenticate(upToken);SecurityContextHolder.getContext().setAuthentication(authentication);final UserDetails userDetails = userDetailsService.loadUserByUsername( username );final String token = jwtTokenUtil.generateToken(userDetails);return token;}// 注册@Overridepublic User register( User userToAdd ) {final String username = userToAdd.getUsername();if( userRepository.findByUsername(username)!=null ) {return null;}BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();final String rawPassword = userToAdd.getPassword();userToAdd.setPassword( encoder.encode(rawPassword) );return userRepository.save(userToAdd);}
}
  • Spring Security配置类编写(非常重要)

这是一个高度综合的配置类,主要是通过重写 WebSecurityConfigurerAdapter 的部分 configure配置,来实现用户自定义的部分。

/*** @ www.codesheep.cn* 20190312*/
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {@Autowiredprivate UserService userService;@Beanpublic JwtTokenFilter authenticationTokenFilterBean() throws Exception {return new JwtTokenFilter();}@Beanpublic AuthenticationManager authenticationManagerBean() throws Exception {return super.authenticationManagerBean();}@Overrideprotected void configure( AuthenticationManagerBuilder auth ) throws Exception {auth.userDetailsService( userService ).passwordEncoder( new BCryptPasswordEncoder() );}@Overrideprotected void configure( HttpSecurity httpSecurity ) throws Exception {httpSecurity.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and().authorizeRequests().antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // OPTIONS请求全部放行.antMatchers(HttpMethod.POST, "/authentication/**").permitAll()  //登录和注册的接口放行,其他接口全部接受验证.antMatchers(HttpMethod.POST).authenticated().antMatchers(HttpMethod.PUT).authenticated().antMatchers(HttpMethod.DELETE).authenticated().antMatchers(HttpMethod.GET).authenticated();// 使用前文自定义的 Token过滤器httpSecurity.addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter.class);httpSecurity.headers().cacheControl();}
}
  • 编写测试 Controller

登录和注册的 Controller:

/*** @ www.codesheep.cn* 20190312*/
@RestController
public class JwtAuthController {@Autowiredprivate AuthService authService;// 登录@RequestMapping(value = "/authentication/login", method = RequestMethod.POST)public String createToken( String username,String password ) throws AuthenticationException {return authService.login( username, password ); // 登录成功会返回JWT Token给用户}// 注册@RequestMapping(value = "/authentication/register", method = RequestMethod.POST)public User register( @RequestBody User addedUser ) throws AuthenticationException {return authService.register(addedUser);}
}

再编写一个测试权限的 Controller:

/*** @ www.codesheep.cn* 20190312*/
@RestController
public class TestController {// 测试普通权限@PreAuthorize("hasAuthority('ROLE_NORMAL')")@RequestMapping( value="/normal/test", method = RequestMethod.GET )public String test1() {return "ROLE_NORMAL /normal/test接口调用成功!";}// 测试管理员权限@PreAuthorize("hasAuthority('ROLE_ADMIN')")@RequestMapping( value = "/admin/test", method = RequestMethod.GET )public String test2() {return "ROLE_ADMIN /admin/test接口调用成功!";}
}

这里给出两个测试接口用于测试权限相关问题,其中接口 /normal/test需要用户具备普通角色(ROLE_NORMAL)即可访问,而接口/admin/test则需要用户具备管理员角色(ROLE_ADMIN)才可以访问。

接下来启动工程,实验测试看看效果


实验验证

  • 在文章开头我们即在用户表 user中插入了一条用户名为 codesheep的记录,并在用户-角色表 user_roles中给用户 codesheep分配了普通角色(ROLE_NORMAL)和管理员角色(ROLE_ADMIN

  • 接下来进行用户登录,并获得后台向用户颁发的JWT Token

  • 接下来访问权限测试接口

不带 Token直接访问需要普通角色(ROLE_NORMAL)的接口 /normal/test会直接提示访问不通:

而带 Token访问需要普通角色(ROLE_NORMAL)的接口 /normal/test才会调用成功:

同理由于目前用户具备管理员角色,因此访问需要管理员角色(ROLE_ADMIN)的接口 /admin/test也能成功:

接下里我们从用户-角色表里将用户codesheep的管理员权限删除掉,再访问接口 /admin/test,会发现由于没有权限,访问被拒绝了:

经过一系列的实验过程,也达到了我们的预期!


写在最后

本文涉及的东西还是蛮多的,最后我们也将本文的实验源码放在 Github上,需要的可以自取:源码下载地址

由于能力有限,若有错误或者不当之处,还请大家批评指正,一起学习交流!

  • My Personal Blog:CodeSheep 程序羊


转载于:https://my.oschina.net/hansonwang99/blog/3022293

基于Spring Security和 JWT的权限系统设计相关推荐

  1. 基于Spring Security与JWT实现单点登录

    基于RBAC的权限管理 RBAC(Role-Based Access Control):基于角色的访问控制 当前项目中,RBAC具体的表现为: 管理员表:ams_admin 角色表:ams_role ...

  2. java oauth sso 源码_基于Spring Security Oauth2的SSO单点登录+JWT权限控制实践

    概 述 在前文<基于Spring Security和 JWT的权限系统设计>之中已经讨论过基于 Spring Security和 JWT的权限系统用法和实践,本文则进一步实践一下基于 Sp ...

  3. 基于 Spring Security 的开源统一角色访问控制系统 URACS

    URACS Java语言开发的统一角色访问控制系统(Unified Role Access Control System),基于Spring Security 3实现的权限控制系统 程序框架版本说明: ...

  4. 基于 Spring Security OAuth2和 JWT 构建保护微服务系统

    我们希望自己的微服务能够在用户登录之后才可以访问,而单独给每个微服务单独做用户权限模块就显得很弱了,从复用角度来说是需要重构的,从功能角度来说,也是欠缺的.尤其是前后端完全分离之后,我们的用户信息不一 ...

  5. Spring Security和 JWT两大利器来打造一个简易的权限系统。

    写在前面 关于 Spring Security Web系统的认证和权限模块也算是一个系统的基础设施了,几乎任何的互联网服务都会涉及到这方面的要求.在Java EE领域,成熟的安全框架解决方案一般有 A ...

  6. 基于Spring Security 的Java SaaS应用的权限管理

    1. 概述 权限管理,一般指根据系统设置的安全规则或者安全策略,用户可以访问而且只能访问自己被授权的资源.资源包括访问的页面,访问的数据等,这在传统的应用系统中比较常见.本文介绍的则是基于Saas系统 ...

  7. 基于 Spring Security 搭建用户权限系统(二) - 自定义配置

    说明 本文的目的是如何基于 Spring Security 去扩展实现一个基本的用户权限模块, 内容会覆盖到 Spring Security 常用的配置. 文中涉及到的业务代码是不完善的, 甚至会存在 ...

  8. 基于Spring Security实现权限管理系统

    基于Spring Security实现权限管理系统 稍微复杂一点的后台系统都会涉及到用户权限管理.何谓用户权限?我的理解就是,权限就是对数据(系统的实体类)和数据可进行的操作(增删查改)的集中管理.要 ...

  9. Spring Security整合JWT,实现单点登录,So Easy~!

    前面整理过一篇 SpringBoot Security前后端分离,登录退出等返回json数据,也就是用Spring Security,基于SpringBoot2.1.4 RELEASE前后端分离的情况 ...

最新文章

  1. 这是我见过最通俗易懂的 装饰者模式 讲解了!
  2. java 1.7 新io 实践 NIO2
  3. Android开发者指南(15) —— Managing Virtual Devices
  4. AWS — AWS 上的 DevOps
  5. 新体验小说:作家重新卷入当代历史的一种方式——纪念“新体验小说”倡导一周...
  6. webService上传图片
  7. ionic html5 上传图片,ionic4+angular7+cordova上传图片功能的实例代码
  8. 6.边缘检测:梯度——边缘检测、导数与边缘、什么是梯度_2
  9. 第十五回(二):文会内战平分秋色 树下阔论使坏心焦【林大帅作品】
  10. matlab monte carlo,Monte Carlo Simulation
  11. php逻辑分析,PHP – 字符串逻辑分析 – “X和Y或Z”
  12. 解析常见网络钓鱼攻击方法
  13. Spring Batch 中的 chunk
  14. MySQL中 修改语句使用的关键字是什么_表示修改一个数据库对象的SQL关键字是什么...
  15. HTML怎么给文本添加删除线?(代码教程)
  16. 概念和术语-数学统计学
  17. 蓝桥杯练习算法题(矩形切割成正方形)
  18. python 批量修改后缀名
  19. tomcat控制台不打印异常问题
  20. 超神学院之天河战役计算机,《超神学院之雄兵连 第1季 天河战役篇》

热门文章

  1. canvas 文字颜色_Canvas 超全教程
  2. 天梯赛省赛选拔赛复盘
  3. IPV6 官方文档 解决ipv6 的问题
  4. UNITER多模态预训练模型原理加代码解读
  5. 工作十二年后,开始学习人生第十四种编程语言
  6. Scratch所有积木
  7. avalonia 控件TextBox 及其他控件文本改变事件
  8. solr版本的选择,4.X如何选择?
  9. 2018-GaAN: Gated Attention Networks for Learning on Large and Spatiotemporal Graphs
  10. DAMA数据治理与数据质量--非结构化数据的数据质量管理