作者:王帅@CodeSheep

 

写在前面

关于 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: IntelliJIDEA2018.2.4

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


设计用户和角色

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

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

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

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


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

  • pom.xml 中引入 Spring Security和 JWT所必需的依赖

  1. <dependency>

  2. <groupId>org.springframework.boot</groupId>

  3. <artifactId>spring-boot-starter-security</artifactId>

  4. </dependency>

  5. <dependency>

  6. <groupId>io.jsonwebtoken</groupId>

  7. <artifactId>jjwt</artifactId>

  8. <version>0.9.0</version>

  9. </dependency>

  • 项目配置文件中加入数据库和 JPA等需要的配置

  1. server.port=9991

  2. spring.datasource.driver-class-name=com.mysql.jdbc.Driver

  3. spring.datasource.url=jdbc:mysql://121.196.XXX.XXX:3306/spring_security_jwt?useUnicode=true&characterEncoding=utf-8

  4. spring.datasource.username=root

  5. spring.datasource.password=XXXXXX

  6. logging.level.org.springframework.security=info

  7. spring.jpa.hibernate.ddl-auto=update

  8. spring.jpa.show-sql=true

  9. spring.jackson.serialization.indent_output=true

  • 创建用户、角色实体

用户实体 User

  1. /**

  2. * @ www.codesheep.cn

  3. * 20190312

  4. */

  5. @Entity

  6. public class User implements UserDetails {

  7. @Id

  8. @GeneratedValue

  9. private Long id;

  10. private String username;

  11. private String password;

  12. @ManyToMany(cascade = {CascadeType.REFRESH},fetch = FetchType.EAGER)

  13. private List<Role> roles;

  14. ...

  15. // 下面为实现UserDetails而需要的重写方法!

  16. @Override

  17. public Collection<? extends GrantedAuthority> getAuthorities() {

  18. List<GrantedAuthority> authorities = new ArrayList<>();

  19. for (Role role : roles) {

  20. authorities.add( new SimpleGrantedAuthority( role.getName() ) );

  21. }

  22. return authorities;

  23. }

  24. ...

  25. }

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

角色实体 Role:

  1. /**

  2. * @ www.codesheep.cn

  3. * 20190312

  4. */

  5. @Entity

  6. public class Role {

  7. @Id

  8. @GeneratedValue

  9. private Long id;

  10. private String name;

  11. ... // 省略 getter和 setter

  12. }

  • 创建JWT工具类

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

  1. /**

  2. * @ www.codesheep.cn

  3. * 20190312

  4. */

  5. @Component

  6. public class JwtTokenUtil implements Serializable {

  7. private static final long serialVersionUID = -5625635588908941275L;

  8. private static final String CLAIM_KEY_USERNAME = "sub";

  9. private static final String CLAIM_KEY_CREATED = "created";

  10. public String generateToken(UserDetails userDetails) {

  11. ...

  12. }

  13. String generateToken(Map<String, Object> claims) {

  14. ...

  15. }

  16. public String refreshToken(String token) {

  17. ...

  18. }

  19. public Boolean validateToken(String token, UserDetails userDetails) {

  20. ...

  21. }

  22. ... // 省略部分工具函数

  23. }

  • 创建Token过滤器,用于每次外部对接口请求时的Token处理

  1. /**

  2. * @ www.codesheep.cn

  3. * 20190312

  4. */

  5. @Component

  6. public class JwtTokenFilter extends OncePerRequestFilter {

  7. @Autowired

  8. private UserDetailsService userDetailsService;

  9. @Autowired

  10. private JwtTokenUtil jwtTokenUtil;

  11. @Override

  12. protected void doFilterInternal ( HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

  13. String authHeader = request.getHeader( Const.HEADER_STRING );

  14. if (authHeader != null && authHeader.startsWith( Const.TOKEN_PREFIX )) {

  15. final String authToken = authHeader.substring( Const.TOKEN_PREFIX.length() );

  16. String username = jwtTokenUtil.getUsernameFromToken(authToken);

  17. if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

  18. UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);

  19. if (jwtTokenUtil.validateToken(authToken, userDetails)) {

  20. UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(

  21. userDetails, null, userDetails.getAuthorities());

  22. authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(

  23. request));

  24. SecurityContextHolder.getContext().setAuthentication(authentication);

  25. }

  26. }

  27. }

  28. chain.doFilter(request, response);

  29. }

  30. }

  • Service业务编写

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

  1. public interface AuthService {

  2. User register( User userToAdd );

  3. String login( String username, String password );

  4. }

  1. /**

  2. * @ www.codesheep.cn

  3. * 20190312

  4. */

  5. @Service

  6. public class AuthServiceImpl implements AuthService {

  7. @Autowired

  8. private AuthenticationManager authenticationManager;

  9. @Autowired

  10. private UserDetailsService userDetailsService;

  11. @Autowired

  12. private JwtTokenUtil jwtTokenUtil;

  13. @Autowired

  14. private UserRepository userRepository;

  15. // 登录

  16. @Override

  17. public String login( String username, String password ) {

  18. UsernamePasswordAuthenticationToken upToken = new UsernamePasswordAuthenticationToken( username, password );

  19. final Authentication authentication = authenticationManager.authenticate(upToken);

  20. SecurityContextHolder.getContext().setAuthentication(authentication);

  21. final UserDetails userDetails = userDetailsService.loadUserByUsername( username );

  22. final String token = jwtTokenUtil.generateToken(userDetails);

  23. return token;

  24. }

  25. // 注册

  26. @Override

  27. public User register( User userToAdd ) {

  28. final String username = userToAdd.getUsername();

  29. if( userRepository.findByUsername(username)!=null ) {

  30. return null;

  31. }

  32. BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();

  33. final String rawPassword = userToAdd.getPassword();

  34. userToAdd.setPassword( encoder.encode(rawPassword) );

  35. return userRepository.save(userToAdd);

  36. }

  37. }

  • Spring Security配置类编写(非常重要)

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

  1. /**

  2. * @ www.codesheep.cn

  3. * 20190312

  4. */

  5. @Configuration

  6. @EnableWebSecurity

  7. @EnableGlobalMethodSecurity(prePostEnabled=true)

  8. public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  9. @Autowired

  10. private UserService userService;

  11. @Bean

  12. public JwtTokenFilter authenticationTokenFilterBean() throws Exception {

  13. return new JwtTokenFilter();

  14. }

  15. @Bean

  16. public AuthenticationManager authenticationManagerBean() throws Exception {

  17. return super.authenticationManagerBean();

  18. }

  19. @Override

  20. protected void configure( AuthenticationManagerBuilder auth ) throws Exception {

  21. auth.userDetailsService( userService ).passwordEncoder( new BCryptPasswordEncoder() );

  22. }

  23. @Override

  24. protected void configure( HttpSecurity httpSecurity ) throws Exception {

  25. httpSecurity.csrf().disable()

  26. .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()

  27. .authorizeRequests()

  28. .antMatchers(HttpMethod.OPTIONS, "/**").permitAll() // OPTIONS请求全部放行

  29. .antMatchers(HttpMethod.POST, "/authentication/**").permitAll() //登录和注册的接口放行,其他接口全部接受验证

  30. .antMatchers(HttpMethod.POST).authenticated()

  31. .antMatchers(HttpMethod.PUT).authenticated()

  32. .antMatchers(HttpMethod.DELETE).authenticated()

  33. .antMatchers(HttpMethod.GET).authenticated();

  34. // 使用前文自定义的 Token过滤器

  35. httpSecurity

  36. .addFilterBefore(authenticationTokenFilterBean(), UsernamePasswordAuthenticationFilter

Spring Boot 集成 Spring Security 实现权限认证模块的更多相关文章

  1. Spring Boot集成Spring Data Reids和Spring Session实现Session共享

    首先,需要先集成Redis的支持,参考:http://www.cnblogs.com/EasonJim/p/7805665.html Spring Boot集成Spring Data Redis+Sp ...

  2. SpringBoot系列:Spring Boot集成Spring Cache,使用EhCache

    前面的章节,讲解了Spring Boot集成Spring Cache,Spring Cache已经完成了多种Cache的实现,包括EhCache.RedisCache.ConcurrentMapCac ...

  3. SpringBoot系列:Spring Boot集成Spring Cache,使用RedisCache

    前面的章节,讲解了Spring Boot集成Spring Cache,Spring Cache已经完成了多种Cache的实现,包括EhCache.RedisCache.ConcurrentMapCac ...

  4. Spring boot 集成Spring Security

    依赖jar <dependency> <groupId>org.springframework.cloud</groupId> <artifactId> ...

  5. Spring Boot 集成spring security4

    项目GitHub地址 : https://github.com/FrameReserve/TrainingBoot Spring Boot (三)集成spring security,标记地址: htt ...

  6. Spring boot集成spring session实现session共享

    最近使用spring boot开发一个系统,nginx做负载均衡分发请求到多个tomcat,此时访问页面会把请求分发到不同的服务器,session是存在服务器端,如果首次访问被分发到A服务器,那么se ...

  7. Spring Boot 集成 Spring Security

    1.添加依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId> ...

  8. Spring Boot 集成 Spring Security 入门案例教程

    前言 本文作为入门级的DEMO,完全按照官网实例演示: 项目目录结构 Maven 依赖 <parent> <groupId>org.springframework.boot&l ...

  9. Spring Boot 集成 Spring Security 使用自定义的安全数据源

    编写一个类自定义实现 UserDetailsService 接口 @Service("customUserDetailService") public class CustomUs ...

随机推荐

  1. Eclipse导出包含第三方Jar的工程

    基于第三方开源的Jar包封装了一个工具类,在导出成Jar包后,引用新生成的Jar包,却报找不到类的错误.看了一下生成的Jar包,发现根本没有包含第三方Jar包的相关class.这是导出第三方Jar包时 ...

  2. winform窗体最小化

    const int WM_SYSCOMMAND = 0x112;const int SC_CLOSE = 0xF060;const int SC_MINIMIZE = 0xF020;const int ...

  3. 【spring源码分析】IOC容器初始化(二)

    前言:在[spring源码分析]IOC容器初始化(一)文末中已经提出loadBeanDefinitions(DefaultListableBeanFactory)的重要性,本文将以此为切入点继续分析. ...

  4. Ubuntu通过apt-get安装指定版本和查询软件源有多少个版本

    一.通过apt-get安装指定版本 apt-get install <<package name>>=<<version>> 二.查询指定软件有多少个版 ...

  5. StringUtils工具类常用方法详解

    StringUtils 常用方法 1.isEmpty(String str) 是否为空,空格字符为false2.isNotEmpty(String str) 是否为非空,空格字符为true3.isBl ...

  6. Linux 基础学习:文件权限与种类

    1.文件权限 linux系统中通过 “ls -al”,可查看当前目录的所有文件的详细信息. 第一列代表这个文件的类型与权限: 第一个字符表示文件类型: [d]:表示目录文件 [-]:表示普通文件 [l ...

  7. 配置sonarqube与gitlab sso认证集成

    1.安装插件 sonar插件地址:https://github.com/gabrie-allaigre/sonar-auth-gitlab-plugin 安装插件: 下载插件然后通过maven打包然后 ...

  8. ASP.NET Core 2.1 : 十四.静态文件与访问授权、防盗链

    我的网站的图片不想被公开浏览.下载.盗链怎么办?本文主要通过解读一下ASP.NET Core对于静态文件的处理方式的相关源码,来看一下为什么是wwwroot文件夹,如何修改或新增一个静态文件夹,为什么 ...

  9. 随心测试_软测基础_004<测试人员工作职责>

    接上篇续,依据_软测基础体系:<依据不同的测试对象,选取适合的方法,按照设计的流程完成测试工作,检验整个过程是否达到测试的目的>.“学以致用”,实践于工作职责 常见面试题: —— 诸如以下 ...

  10. XAMPP支持多PHP版本

    在使用xampp进行开发的时候,我们都知道它只能支持一个PHP版本不能像PHPstudy那样方便进行不同版本的切换操作.因此我们手动的对于xampp进行改造一下,使其支持其他PHP版本 1. 从官网上 ...