Spring Boot + Spring Cloud 实现权限管理系统 后端篇(二十五):Spring Security 版本
在线演示
演示地址:http://139.196.87.48:9002/kitty
用户名:admin 密码:admin
技术背景
到目前为止,我们使用的权限认证框架是 Shiro,虽然 Shiro 也足够好用并且简单,但对于 Spring 官方主推的安全框架 Spring Security,用户群也是甚大的,所以我们这里把当前的代码切分出一个 shiro-cloud 分支,作为 Shiro + Spring Cloud 技术的分支代码,dev 和 master 分支将替换为 Spring Security + Spring Cloud 的技术栈,并在后续计划中集成 Spring Security OAuth2 实现单点登录功能。
代码实现
Maven依赖
移除shiro依赖,添加Spring Scurity和JWT依赖包,jwt目前的最新版本是0.9.1。
- <!-- spring security -->
- <dependency>
- <groupId>org.springframework.boot</groupId>
- <artifactId>spring-boot-starter-security</artifactId>
- </dependency>
- <!-- jwt -->
- <dependency>
- <groupId>io.jsonwebtoken</groupId>
- <artifactId>jjwt</artifactId>
- <version>${jwt.version}</version>
- </dependency>
权限注解
替换Shiro的权限注解为Spring Security的权限注解。
格式如下:
@PreAuthorize("hasAuthority('sys:menu:view')")
SysMenuController.java
- package com.louis.kitty.admin.controller;
- import java.util.List;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.access.prepost.PreAuthorize;
- import org.springframework.web.bind.annotation.GetMapping;
- import org.springframework.web.bind.annotation.PostMapping;
- import org.springframework.web.bind.annotation.RequestBody;
- import org.springframework.web.bind.annotation.RequestMapping;
- import org.springframework.web.bind.annotation.RequestParam;
- import org.springframework.web.bind.annotation.RestController;
- import com.louis.kitty.admin.model.SysMenu;
- import com.louis.kitty.admin.sevice.SysMenuService;
- import com.louis.kitty.core.http.HttpResult;
- /**
- * 菜单控制器
- * @author Louis
- * @date Oct 29, 2018
- */
- @RestController
- @RequestMapping("menu")
- public class SysMenuController {
- @Autowired
- private SysMenuService sysMenuService;
- @PreAuthorize("hasAuthority('sys:menu:add') AND hasAuthority('sys:menu:edit')")
- @PostMapping(value="/save")
- public HttpResult save(@RequestBody SysMenu record) {
- return HttpResult.ok(sysMenuService.save(record));
- }
- @PreAuthorize("hasAuthority('sys:menu:delete')")
- @PostMapping(value="/delete")
- public HttpResult delete(@RequestBody List<SysMenu> records) {
- return HttpResult.ok(sysMenuService.delete(records));
- }
- @PreAuthorize("hasAuthority('sys:menu:view')")
- @GetMapping(value="/findNavTree")
- public HttpResult findNavTree(@RequestParam String userName) {
- return HttpResult.ok(sysMenuService.findTree(userName, ));
- }
- @PreAuthorize("hasAuthority('sys:menu:view')")
- @GetMapping(value="/findMenuTree")
- public HttpResult findMenuTree() {
- return HttpResult.ok(sysMenuService.findTree(null, ));
- }
- }
Spring Security注解默认是关闭的,可以通过在配置类添加以下注解开启。
- @EnableGlobalMethodSecurity(prePostEnabled = true)
安全配置
添加安全配置类, 继承 WebSecurityConfigurerAdapter,配置URL验证策略和相关过滤器以及自定义的登录验证组件。
WebSecurityConfig.java
- package com.louis.kitty.admin.config;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.context.annotation.Bean;
- import org.springframework.context.annotation.Configuration;
- import org.springframework.http.HttpMethod;
- import org.springframework.security.authentication.AuthenticationManager;
- import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
- import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
- import org.springframework.security.config.annotation.web.builders.HttpSecurity;
- import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
- import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
- import org.springframework.security.web.authentication.logout.HttpStatusReturningLogoutSuccessHandler;
- import com.louis.kitty.admin.security.JwtAuthenticationFilter;
- import com.louis.kitty.admin.security.JwtAuthenticationProvider;
- /**
- * Spring Security Config
- * @author Louis
- * @date Nov 20, 2018
- */
- @Configuration
- @EnableWebSecurity
- @EnableGlobalMethodSecurity(prePostEnabled = true)
- public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
- @Autowired
- private UserDetailsService userDetailsService;
- @Override
- public void configure(AuthenticationManagerBuilder auth) throws Exception {
- // 使用自定义身份验证组件
- auth.authenticationProvider(new JwtAuthenticationProvider(userDetailsService));
- }
- @Override
- protected void configure(HttpSecurity http) throws Exception {
- // 禁用 csrf, 由于使用的是JWT,我们这里不需要csrf
- http.cors().and().csrf().disable()
- .authorizeRequests()
- // 跨域预检请求
- .antMatchers(HttpMethod.OPTIONS, "/**").permitAll()
- // web jars
- .antMatchers("/webjars/**").permitAll()
- // 查看SQL监控(druid)
- .antMatchers("/druid/**").permitAll()
- // 首页和登录页面
- .antMatchers("/").permitAll()
- .antMatchers("/login").permitAll()
- // swagger
- .antMatchers("/swagger-ui.html").permitAll()
- .antMatchers("/swagger-resources").permitAll()
- .antMatchers("/v2/api-docs").permitAll()
- .antMatchers("/webjars/springfox-swagger-ui/**").permitAll()
- // 验证码
- .antMatchers("/captcha.jpg**").permitAll()
- // 服务监控
- .antMatchers("/actuator/**").permitAll()
- // 其他所有请求需要身份认证
- .anyRequest().authenticated();
- // 退出登录处理器
- http.logout().logoutSuccessHandler(new HttpStatusReturningLogoutSuccessHandler());
- // 登录认证过滤器
- http.addFilterBefore(new JwtAuthenticationFilter(authenticationManager()), UsernamePasswordAuthenticationFilter.class);
- }
- @Bean
- @Override
- public AuthenticationManager authenticationManager() throws Exception {
- return super.authenticationManager();
- }
- }
登录验证组件
继承 DaoAuthenticationProvider, 实现自定义的登录验证组件,覆写密码验证逻辑。
JwtAuthenticationProvider.java
- package com.louis.kitty.admin.security;
- import org.springframework.security.authentication.BadCredentialsException;
- import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
- import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
- import org.springframework.security.core.AuthenticationException;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import com.louis.kitty.admin.util.PasswordEncoder;
- /**
- * 身份验证提供者
- * @author Louis
- * @date Nov 20, 2018
- */
- public class JwtAuthenticationProvider extends DaoAuthenticationProvider {
- public JwtAuthenticationProvider(UserDetailsService userDetailsService) {
- setUserDetailsService(userDetailsService);
- }
- @Override
- protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication)
- throws AuthenticationException {
- if (authentication.getCredentials() == null) {
- logger.debug("Authentication failed: no credentials provided");
- throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
- }
- String presentedPassword = authentication.getCredentials().toString();
- String salt = ((JwtUserDetails) userDetails).getSalt();
- // 覆写密码验证逻辑
- if (!new PasswordEncoder(salt).matches(userDetails.getPassword(), presentedPassword)) {
- logger.debug("Authentication failed: password does not match stored value");
- throw new BadCredentialsException(messages.getMessage("AbstractUserDetailsAuthenticationProvider.badCredentials", "Bad credentials"));
- }
- }
- }
用户认证信息查询组件
实现 UserDetailsService 接口,定义用户认证信息查询组件,用于获取认证所需的用户信息和授权信息。
UserDetailsServiceImpl.java
- package com.louis.kitty.admin.security;
- import java.util.List;
- import java.util.Set;
- import java.util.stream.Collectors;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.core.GrantedAuthority;
- import org.springframework.security.core.userdetails.UserDetails;
- import org.springframework.security.core.userdetails.UserDetailsService;
- import org.springframework.security.core.userdetails.UsernameNotFoundException;
- import org.springframework.stereotype.Service;
- import com.louis.kitty.admin.model.SysUser;
- import com.louis.kitty.admin.sevice.SysUserService;
- /**
- * 用户登录认证信息查询
- * @author Louis
- * @date Nov 20, 2018
- */
- @Service
- public class UserDetailsServiceImpl implements UserDetailsService {
- @Autowired
- private SysUserService sysUserService;
- @Override
- public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
- SysUser user = sysUserService.findByName(username);
- if (user == null) {
- throw new UsernameNotFoundException("该用户不存在");
- }
- // 用户权限列表,根据用户拥有的权限标识与如 @PreAuthorize("hasAuthority('sys:menu:view')") 标注的接口对比,决定是否可以调用接口
- Set<String> permissions = sysUserService.findPermissions(user.getName());
- List<GrantedAuthority> grantedAuthorities = permissions.stream().map(GrantedAuthorityImpl::new).collect(Collectors.toList());
- return new JwtUserDetails(user.getName(), user.getPassword(), user.getSalt(), grantedAuthorities);
- }
- }
用户认证信息封装
上面 UserDetailsService 查询的信息需要封装到实现 UserDetails 接口的封装对象里。
JwtUserDetails.java
- package com.louis.kitty.admin.security;
- import java.util.Collection;
- import org.springframework.security.core.GrantedAuthority;
- import org.springframework.security.core.userdetails.UserDetails;
- import com.fasterxml.jackson.annotation.JsonIgnore;
- /**
- * 安全用户模型
- * @author Louis
- * @date Nov 20, 2018
- */
- public class JwtUserDetails implements UserDetails {
- private static final long serialVersionUID = 1L;
- private String username;
- private String password;
- private String salt;
- private Collection<? extends GrantedAuthority> authorities;
- JwtUserDetails(String username, String password, String salt, Collection<? extends GrantedAuthority> authorities) {
- this.username = username;
- this.password = password;
- this.salt = salt;
- this.authorities = authorities;
- }
- @Override
- public String getUsername() {
- return username;
- }
- @JsonIgnore
- @Override
- public String getPassword() {
- return password;
- }
- public String getSalt() {
- return salt;
- }
- @Override
- public Collection<? extends GrantedAuthority> getAuthorities() {
- return authorities;
- }
- @JsonIgnore
- @Override
- public boolean isAccountNonExpired() {
- return true;
- }
- @JsonIgnore
- @Override
- public boolean isAccountNonLocked() {
- return true;
- }
- @JsonIgnore
- @Override
- public boolean isCredentialsNonExpired() {
- return true;
- }
- @JsonIgnore
- @Override
- public boolean isEnabled() {
- return true;
- }
- }
登录接口
因为我们没有使用内置的 formLogin 登录处理过滤器,所以需要调用登录认证流程,修改登录接口,加入系统登录认证调用。
SysLoginController.java
- /**
- * 登录接口
- */
- @PostMapping(value = "/login")
- public HttpResult login(@RequestBody LoginBean loginBean, HttpServletRequest request) throws IOException {
- String username = loginBean.getAccount();
- String password = loginBean.getPassword();
- String captcha = loginBean.getCaptcha();...
// 系统登录认证- JwtAuthenticatioToken token = SecurityUtils.login(request, username, password, authenticationManager);
- return HttpResult.ok(token);
- }
Spring Security 的登录认证过程是通过调用 AuthenticationManager 的 authenticate(token) 方法实现的。
登录流程中主要是返回一个认证好的 Authentication 对象,然后保存到上下文供后续进行授权的时候使用。
登录认证成功之后,会利用JWT生成 token 返回给客户端,后续的访问都需要携带此 token 来进行认证。
SecurityUtils.java
- /**
- * 系统登录认证
- * @param request
- * @param username
- * @param password
- * @param authenticationManager
- * @return
- */
- public static JwtAuthenticatioToken login(HttpServletRequest request, String username, String password, AuthenticationManager authenticationManager) {
- JwtAuthenticatioToken token = new JwtAuthenticatioToken(username, password);
- token.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
- // 执行登录认证过程
- Authentication authentication = authenticationManager.authenticate(token);
- // 认证成功存储认证信息到上下文
- SecurityContextHolder.getContext().setAuthentication(authentication);
- // 生成令牌并返回给客户端
- token.setToken(JwtTokenUtils.generateToken(authentication));
- return token;
- }
令牌生成器
令牌生成器主要是利用JWT生成所需的令牌,部分代码如下。
JwtTokenUtils.java
- /**
- * JWT工具类
- * @author Louis
- * @date Nov 20, 2018
- */
- public class JwtTokenUtils implements Serializable {
- /**
- * 生成令牌
- * @param userDetails 用户
- * @return 令牌
- */
- public static String generateToken(Authentication authentication) {
- Map<String, Object> claims = new HashMap<>(3);
- claims.put(USERNAME, SecurityUtils.getUsername(authentication));
- claims.put(CREATED, new Date());
- claims.put(AUTHORITIES, authentication.getAuthorities());
- return generateToken(claims);
- }
- /**
- * 从数据声明生成令牌
- * @param claims 数据声明
- * @return 令牌
- */
- private static String generateToken(Map<String, Object> claims) {
- Date expirationDate = new Date(System.currentTimeMillis() + EXPIRE_TIME);
- return Jwts.builder().setClaims(claims).setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, SECRET).compact();
- }
- }
登录认证过滤器
登录认证过滤器继承 BasicAuthenticationFilter,在访问任何URL的时候会被此过滤器拦截,通过调用 SecurityUtils.checkAuthentication(request) 检查登录状态。
JwtAuthenticationFilter.java
- package com.louis.kitty.admin.security;
- import java.io.IOException;
- import javax.servlet.FilterChain;
- import javax.servlet.ServletException;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.security.authentication.AuthenticationManager;
- import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
- import com.louis.kitty.admin.util.SecurityUtils;
- /**
- * 登录认证过滤器
- * @author Louis
- * @date Nov 20, 2018
- */
- public class JwtAuthenticationFilter extends BasicAuthenticationFilter {
- @Autowired
- public JwtAuthenticationFilter(AuthenticationManager authenticationManager) {
- super(authenticationManager);
- }
- @Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
- // 获取token, 并检查登录状态
- SecurityUtils.checkAuthentication(request);
- chain.doFilter(request, response);
- }
- }
登录认证检查
登录验证检查是通过 SecurityUtils.checkAuthentication(request) 来完成的。
SecurityUtils.java
- /**
- * 获取令牌进行认证
- * @param request
- */
- public static void checkAuthentication(HttpServletRequest request) {
- // 获取令牌并根据令牌获取登录认证信息
- Authentication authentication = JwtTokenUtils.getAuthenticationeFromToken(request);
- // 设置登录认证信息到上下文
- SecurityContextHolder.getContext().setAuthentication(authentication);
- }
上面的登录验证是通过 JwtTokenUtils.getAuthenticationeFromToken(request),来验证令牌并返回登录信息的。
JwtTokenUtils.java
- /**
- * 根据请求令牌获取登录认证信息
- * @param token 令牌
- * @return 用户名
- */
- public static Authentication getAuthenticationeFromToken(HttpServletRequest request) {
- Authentication authentication = null;
- // 获取请求携带的令牌
- String token = JwtTokenUtils.getToken(request);
- if(token != null) {
- // 请求令牌不能为空
- if(SecurityUtils.getAuthentication() == null) {
- // 上下文中Authentication为空
- Claims claims = getClaimsFromToken(token);
- if(claims == null) {
- return null;
- }
- String username = claims.getSubject();
- if(username == null) {
- return null;
- }
- if(isTokenExpired(token)) {
- return null;
- }
- Object authors = claims.get(AUTHORITIES);
- List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>();
- if (authors != null && authors instanceof List) {
- for (Object object : (List) authors) {
- authorities.add(new GrantedAuthorityImpl((String) ((Map) object).get("authority")));
- }
- }
- authentication = new JwtAuthenticatioToken(username, null, authorities, token);
- } else {
- if(validateToken(token, SecurityUtils.getUsername())) {
- // 如果上下文中Authentication非空,且请求令牌合法,直接返回当前登录认证信息
- authentication = SecurityUtils.getAuthentication();
- }
- }
- }
- return authentication;
- }
清除Shiro配置
清除掉 config 包下的 ShiroConfig 配置类。
清除 oautho2 包下有关 Shiro 的相关代码。
清除掉 sys_token 表和相关操作代码。
源码下载
后端:https://gitee.com/liuge1988/kitty
前端:https://gitee.com/liuge1988/kitty-ui.git
作者:朝雨忆轻尘
出处:https://www.cnblogs.com/xifengxiaoma/
版权所有,欢迎转载,转载请注明原文作者及出处。
Spring Boot + Spring Cloud 实现权限管理系统 后端篇(二十五):Spring Security 版本的更多相关文章
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(十五):系统服务监控
系统服务监控 新建监控工程 新建Spring Boot项目,取名 kitty-monitor,结构如下. 添加项目依赖 添加 spring boot admin 的相关依赖. pom.xml < ...
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(十九):服务消费(Ribbon、Feign)
技术背景 上一篇教程中,我们利用Consul注册中心,实现了服务的注册和发现功能,这一篇我们来聊聊服务的调用.单体应用中,代码可以直接依赖,在代码中直接调用即可,但在微服务架构是分布式架构,服务都运行 ...
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(十八):注册中心(Spring Cloud Consul)
什么是 Consul Consul 是 HashiCorp 公司推出的开源工具,用于实现分布式系统的服务发现与配置.与其它分布式服务注册与发现的方案,Consul 的方案更“一站式”,内置了服务注册与 ...
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(十六):容器部署项目
容器部署项目 这一章我们引入docker,采用docker容器的方式部署我们的项目. 首先需要有一个linux环境,并且安装 java 和 maven 以及 docker 环境,这个教程多如牛毛,不再 ...
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(十四):项目打包部署
项目打包部署 安装MySQL镜像 注意:如果使用docker镜像安装MySQL,也需要在前端部署主机安装MySQL,因为备份还原功能是使用MySQL的本地命令进行操作的. 下载镜像 执行以下命令,拉取 ...
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(十二):解决跨域问题
什么是跨域? 同源策略是浏览器的一个安全功能,不同源的客户端脚本在没有明确授权的情况下,不能读写对方资源. 同源策略是浏览器安全的基石. 如果一个请求地址里面的协议.域名和端口号都相同,就属于同源. ...
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(十):接口服务整理
通用操作 通用操作是指一般的增删改查操作,逻辑大体都是一致的,所以统一抽象到CURD接口,需要用到CURD的表直接实现接口就可以了. 通用操作主要有以下几个: 保存操作 /** * 保存操作 * @p ...
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(一):Kitty 系统介绍
在线演示 演示地址:http://139.196.87.48:9002/kitty 用户名:admin 密码:admin 温馨提示: 有在演示环境删除数据的童鞋们,如果可以的话,麻烦动动小指,右键头像 ...
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(二十三):配置中心(Config、Bus)
在线演示 演示地址:http://139.196.87.48:9002/kitty 用户名:admin 密码:admin 技术背景 如今微服务架构盛行,在分布式系统中,项目日益庞大,子项目日益增多,每 ...
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(二十):服务熔断(Hystrix、Turbine)
在线演示 演示地址:http://139.196.87.48:9002/kitty 用户名:admin 密码:admin 雪崩效应 在微服务架构中,由于服务众多,通常会涉及多个服务层级的调用,而一旦基 ...
随机推荐
- Sublime Text3快捷键大全
选择类 Ctrl+D 选中光标所占的文本,继续操作则会选中下一个相同的文本. Alt+F3 选中文本按下快捷键,即可一次性选择全部的相同文本进行同时编辑.举个栗子:快速选中并更改所有相同的变量名.函数 ...
- Boost::bind使用详解
1.Boost::bind 在STL中,我们经常需要使用bind1st,bind2st函数绑定器和fun_ptr,mem_fun等函数适配器,这些函数绑定器和函数适配器使用起来比较麻烦,需要根据是全局 ...
- 20172325 2018-2019-2 《Java程序设计》第九周学习总结
20172325 2018-2019-2 <Java程序设计>第九周学习总结 教材学习内容总结 图的定义 图是由顶点集(VertexSet)和边集(EdgeSet)组成,针对图G,顶点集和 ...
- Unity3D使用EasyMovieTexture插件播放视频
Unity3D对于视频的播放兼容个人感觉很差劲,之前写过一篇使用Unity3D自己自带的一些功能去播放视频,链接如下: http://www.cnblogs.com/xiaoyulong/p/8627 ...
- Struts2学习第四天——拦截器及文件上传
1.概述 Struts2的很多核心功能都是由拦截器完成的. 拦截器很好的实现了AOP的编程思想,在动作的执行之前和结果的返回之后,做拦截处理. 2.struts2的默认拦截器栈 3.自定义拦截器 St ...
- spring boot2 集成Redis
1. 引入依赖 <parent> <groupId>org.springframework.boot</groupId> <artifactId>spr ...
- Django URLConf 进阶
Django处理一个请求 项目启动后根据 settings ROOT_URLCONF 决定项目根URLconf urlpatterns是django.conf.urls.url()实例的一个Pyth ...
- Burpsuite常用模块详解以及渗透测试上的运用
0x00前言 哪有什么前言,大家好,我是浅安.QQ:320229344... 0x01 JDK的安装,以及Burpsuite的成功开启. burpsuite基于JAVA环境才能正常运行的.所以要先安装 ...
- 设计模式总结(Java)—— 单例模式
1. 定义 为了确保一个类有且仅有一个实例,而且自行实例化并向整个系统提供这个实例. 2. 使用场景 确保某个类有且只有一个对象的场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有 ...
- react-router V4中的url参数
概述 之前写过react在router中传递数据的2种方法,但是有些细节没有理清楚,现在补上,记录下来,供以后开发时参考,相信对其他人也有用. 参考资料:stackoverflow react rou ...