摘自:

  https://www.cnblogs.com/hhhshct/p/9726378.html

  https://blog.csdn.net/weixin_42849689/article/details/89957823

  https://blog.csdn.net/zhaoxichen_10/article/details/88713799

  http://www.imooc.com/article/287214

一、Spring Security简介

  Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。它是一个轻量级的安全框架,它确保基于Spring的应用程序提供身份验证和授权支持。它与Spring MVC有很好地集成,并配备了流行的安全算法实现捆绑在一起。安全主要包括两个操作“认证”与“验证”(有时候也会叫做权限控制)。“认证”是为用户建立一个其声明的角色的过程,这个角色可以一个用户、一个设备或者一个系统。“验证”指的是一个用户在你的应用中能够执行某个操作。在到达授权判断之前,角色已经在身份认证过程中建立了。

  用户登陆,会被AuthenticationProcessingFilter拦截,调用AuthenticationManager的实现,而且AuthenticationManager会调用ProviderManager来获取用户验证信息(不同的Provider调用的服务不同,因为这些信息可以是在数据库上,可以是在LDAP服务器上,可以是xml配置文件上等),如果验证通过后会将用户的权限信息封装一个User放到spring的全局缓存SecurityContextHolder中,以备后面访问资源时使用。
  访问资源(即授权管理),访问url时,会通过AbstractSecurityInterceptor拦截器拦截,其中会调用FilterInvocationSecurityMetadataSource的方法来获取被拦截url所需的全部权限,在调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则返回,权限不够则报错并调用权限不足页面。

二、Spring Security的执行过程

三、Spring Security代码实现

Spring Security的核心配置类是 WebSecurityConfig,抽象类
这是权限管理启动的入口,这里我们自定义一个实现类去它。然后编写我们需要处理的控制逻辑。
下面是代码,里面写的注释也比较详细。在里面还依赖了几个自定义的类,都是必须配置的。分别是
userService,
myFilterInvocationSecurityMetadataSource,
myAccessDecisionManager,

authenticationAccessDeniedHandler

3.1 WebSecurityConfig

package com.example.demo.config;

import java.io.IOException;
import java.io.PrintWriter; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.config.annotation.ObjectPostProcessor;
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.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import com.example.demo.service.UserService;
/**
* spring-security权限管理的核心配置
* @author wjqhuaxia
*
*/
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true) //全局
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
private UserService userService;//实现了UserDetailsService接口
@Autowired
private MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;//权限过滤器(当前url所需要的访问权限)
@Autowired
private MyAccessDecisionManager myAccessDecisionManager;//权限决策器
@Autowired
private AuthenticationAccessDeniedHandler authenticationAccessDeniedHandler;//自定义错误(403)返回数据 /**
* 自定义的加密算法
* @return
*/
@Bean
public PasswordEncoder myPasswordEncoder() {
return new MyPasswordEncoder();
}
/**
* 配置userDetails的数据源,密码加密格式
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userService).passwordEncoder(myPasswordEncoder());
}
/**
* 配置放行的资源
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring()
.antMatchers("/index.html", "/static/**","/loginPage","/register")
// 给 swagger 放行;不需要权限能访问的资源
.antMatchers("/swagger-ui.html", "/swagger-resources/**", "/images/**", "/webjars/**", "/v2/api-docs", "/configuration/ui", "/configuration/security");
} /**
* 这段配置,我认为就是配置Security的认证策略, 每个模块配置使用and结尾。
authorizeRequests()配置路径拦截,表明路径访问所对应的权限,角色,认证信息。
formLogin()对应表单认证相关的配置
logout()对应了注销相关的配置
httpBasic()可以配置basic登录
*/
/**
* HttpSecurity包含了原数据(主要是url)
* 1.通过withObjectPostProcessor将MyFilterInvocationSecurityMetadataSource和MyAccessDecisionManager注入进来
* 2.此url先被MyFilterInvocationSecurityMetadataSource处理,然后 丢给 MyAccessDecisionManager处理
* 3.如果不匹配,返回 MyAccessDeniedHandler
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
// authorizeRequests()配置路径拦截,表明路径访问所对应的权限,角色,认证信息
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
o.setAccessDecisionManager(myAccessDecisionManager);
return o;
}
})
.and()
// formLogin()对应表单认证相关的配置
.formLogin()
.loginPage("/loginPage")
.loginProcessingUrl("/login")
.usernameParameter("username")
.passwordParameter("password")
.permitAll()
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
StringBuffer sb = new StringBuffer();
sb.append("{\"status\":\"error\",\"msg\":\"");
if (e instanceof UsernameNotFoundException || e instanceof BadCredentialsException) {
sb.append("用户名或密码输入错误,登录失败!");
} else {
sb.append("登录失败!");
}
sb.append("\"}");
out.write(sb.toString());
out.flush();
out.close();
}
}).successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
String s = "{\"status\":\"success\",\"msg\":\"登陆成功\"}";
out.write(s);
out.flush();
out.close();
}
}).and()
// logout()对应了注销相关的配置
.logout()
.permitAll()
.and()
.csrf()
.disable()
.exceptionHandling()
.accessDeniedHandler(authenticationAccessDeniedHandler);
}
}

3.2 UserService

UserServiceImpl实现了UserDetailsService接口中的loadUserByUsername方法,方法执行成功后返回UserDetails对象,为构建Authentication对象提供必须的信息。UserDetails中包含了用户名,密码,角色等信息。

package com.example.demo.service.impl;

import java.util.ArrayList;
import java.util.List; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import com.example.demo.dao.PermissionMapper;
import com.example.demo.dao.RoleMapper;
import com.example.demo.dao.UserMapper;
import com.example.demo.model.Permission;
import com.example.demo.model.User;
import com.example.demo.service.UserService;
/**
* 实现了UserDetailsService接口中的loadUserByUsername方法
* 执行登录,构建Authentication对象必须的信息,
* 如果用户不存在,则抛出UsernameNotFoundException异常
* @author wjqhuaxia
*
*/
@Service
public class UserServiceImpl implements UserService { @Autowired
private PermissionMapper permissionMapper;
@Autowired
private RoleMapper roleMapper;
@Autowired
private UserMapper userMapper;
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.selectByUsername(username);
if (user != null) {
List<Permission> permissions = permissionMapper.findByUserId(user.getId());
List<GrantedAuthority> grantedAuthorities = new ArrayList <>();
for (Permission permission : permissions) {
if (permission != null && permission.getPermissionname()!=null) { GrantedAuthority grantedAuthority = new SimpleGrantedAuthority(permission.getPermissionname());
grantedAuthorities.add(grantedAuthority);
}
}
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), grantedAuthorities);
} else {
throw new UsernameNotFoundException("username: " + username + " do not exist!");
}
} @Transactional
@Override
public void userRegister(String username, String password) {
User user = new User();
user.setUsername(passwordEncoder.encode(username));
user.setPassword(password);
userMapper.insert(user);
User rtnUser =userMapper.selectByUsername(username);
//注册成功默认给用户的角色是user
roleMapper.insertUserRole(rtnUser.getId(), 2);
} }

3.3 MyFilterInvocationSecurityMetadataSource

自定义权限过滤器,继承了 SecurityMetadataSource(权限资源接口),过滤所有请求,核查这个请求需要的访问权限;主要实现Collection<ConfigAttribute> getAttributes(Object o)方法,此方法中可编写用户逻辑,根据用户预先设定的用户权限列表,返回访问此url需要的权限列表。

package com.example.demo.config;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import com.example.demo.dao.PermissionMapper;
import com.example.demo.model.Permission;
/**
* 自定义权限过滤器
* FilterInvocationSecurityMetadataSource(权限资源过滤器接口)继承了 SecurityMetadataSource(权限资源接口)
* Spring Security是通过SecurityMetadataSource来加载访问时所需要的具体权限;Metadata是元数据的意思。
* 自定义权限资源过滤器,实现动态的权限验证
* 它的主要责任就是当访问一个url时,返回这个url所需要的访问权限
* @author wjqhuaxia
*
*/
@Service
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private static final Logger log = LoggerFactory.getLogger(MyFilterInvocationSecurityMetadataSource.class); @Autowired
private PermissionMapper permissionMapper; private HashMap<String, Collection<ConfigAttribute>> map = null; /**
* 加载权限表中所有权限
*/
public void loadResourceDefine() {
map = new HashMap<String, Collection<ConfigAttribute>>(); List<Permission> permissions = permissionMapper.findAll();
for (Permission permission : permissions) {
if(StringUtils.isEmpty(permission.getPermissionname())){
continue;
}
if(StringUtils.isEmpty(permission.getUrl())){
continue;
}
ConfigAttribute cfg = new SecurityConfig(permission.getPermissionname());
List<ConfigAttribute> list = new ArrayList<>();
list.add(cfg);
// TODO:如果一个url对应多个权限,这里有问题
map.put(permission.getUrl(), list);
} } /**
* 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法, 用来判定用户
* 是否有此权限。如果不在权限表中则放行。
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
if (map == null) {
loadResourceDefine();
}
// object 中包含用户请求的request的信息
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
for (Entry<String, Collection<ConfigAttribute>> entry : map.entrySet()) {
String url = entry.getKey();
if (new AntPathRequestMatcher(url).matches(request)) {
return map.get(url);
}
}
/**
* @Author: Galen
* @Description: 如果本方法返回null的话,意味着当前这个请求不需要任何角色就能访问
* 此处做逻辑控制,如果没有匹配上的,返回一个默认具体权限,防止漏缺资源配置
**/
log.info("当前访问路径是{},这个url所需要的访问权限是{}", request.getRequestURL(), "ROLE_LOGIN");
return SecurityConfig.createList("ROLE_LOGIN");
}
/**
* 此处方法如果做了实现,返回了定义的权限资源列表,
* Spring Security会在启动时校验每个ConfigAttribute是否配置正确,
* 如果不需要校验,这里实现方法,方法体直接返回null即可
*/
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
/**
* 方法返回类对象是否支持校验,
* web项目一般使用FilterInvocation来判断,或者直接返回true
*/
@Override
public boolean supports(Class<?> clazz) {
return true;
} }

3.4 AuthenticationAccessDeniedHandler

自定义权限决策管理器,需要实现AccessDecisionManager 的 void decide(Authentication auth, Object object, Collection<ConfigAttribute> cas) 方法,在上面的过滤器中,我们已经得到了访问此url需要的权限;那么,decide方法,先查询此用户当前拥有的权限,然后与上面过滤器核查出来的权限列表作对比,以此判断此用户是否具有这个访问权限,决定去留!所以顾名思义为权限决策器。

package com.example.demo.config;

import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component; import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 拒签(403响应)处理器
* Denied是拒签的意思
* @author wjqhuaxia
*
*/
@Component
public class AuthenticationAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse resp, AccessDeniedException e) throws IOException, ServletException {
resp.setStatus(HttpServletResponse.SC_FORBIDDEN);
resp.setContentType("application/json;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.write("{\"status\":\"error\",\"msg\":\"权限不足,请联系管理员!\"}");
out.flush();
out.close();
}
}

【权限管理】springboot集成security的更多相关文章

  1. 基于Springboot集成security、oauth2实现认证鉴权、资源管理

    1.Oauth2简介 OAuth(开放授权)是一个开放标准,允许用户授权第三方移动应用访问他们存储在另外的服务提供者上的信息,而不需要将用户名和密码提供给第三方移动应用或分享他们数据的所有内容,OAu ...

  2. SpringBoot集成security

    本文就SpringBoot集成Security的使用步骤做出解释说明.

  3. Spring安全权限管理(Spring Security)

    1.spring Security简要介绍 Spring Security以前叫做acegi,是后来才成为Spring的一个子项目,也是目前最为流行的一个安全权限管理框架,它与Spring紧密结合在一 ...

  4. SpringBoot集成Security,JWT,Swagger全分析

    GitHub地址: https://github.com/li-jun0201/springsecuritydemo本项目采用SpringBoot1.5.9, SpringSecurity,JWT, ...

  5. 【权限管理】Spring Security 执行流程

    转自:https://blog.csdn.net/weixin_37689658/article/details/92752890 1.基本配置使用 (1)创建配置类 创建一个配置类SecurityC ...

  6. (十三)整合 SpringSecurity 框架,实现用户权限管理

    整合 SpringSecurity 框架,实现用户权限管理 1.Security简介 1.1 基础概念 1.2 核心API解读 2.SpringBoot整合SpringSecurity 2.1 流程描 ...

  7. SpringSecurity实现权限管理和页面导航栏动态实现

    用户模块. 3 1.1  需求:获取用户名. 3 1.1.1     分析. 3 1.1.2     服务端获取用户信息. 4 1.1.3     页面获取用户信息. 5 1.2  给用户分配角色. ...

  8. gitblit服务器:用户、团队、权限管理

    在日常开发工作中,我们通常使用版本控制软件管理团队的源代码,常用的SVN.Git.与SVN相比,Git有分支的概念,可以从主分支创建开发分支,在开发分支测试没有问题之后,再合并到主分支上去,从而避免了 ...

  9. springBoot整合spring security+JWT实现单点登录与权限管理--筑基中期

    写在前面 在前一篇文章当中,我们介绍了springBoot整合spring security单体应用版,在这篇文章当中,我将介绍springBoot整合spring secury+JWT实现单点登录与 ...

随机推荐

  1. C语言:获取汉字的编码

    #include <stdio.h> #include <locale.h> #include <wchar.h> int main() { setlocale(L ...

  2. 家庭账本开发day04

    对之前的构架进行修改,对成员类新加属性余额,在进行账单的新增时 ,对余额进行相应的修改.并且对账单类加入属性:id方便之后的查询和 删除操作

  3. Beautifulsoup网页解析——爬取豆瓣排行榜分类接口

    我们在网页爬取的过程中,会通过requests成功的获取到所需要的信息,而且,在返回的网页信息中,也是通过HTML代码的形式进行展示的.HTML代码都是通过固定的标签组合来实现页面信息的展示,所以,最 ...

  4. 7Java基础补充

    1.标准Java bean写法 包括:private修饰的成员变量.getter和setter以及无参和有多个参数的有参构造方法 2.String原理 String底层是字节数组byte[]. Str ...

  5. Guava - 字符串处理

    Joiner 连接 比如,有这样一个集合[1,2,3,4,5,7,null],想把这个集合转换成以#分割的字符串,并过滤掉集合中的空元素 List<Integer> eleList = A ...

  6. 2021 NOI冬令营

    2021 NOI冬令营 -- 本来想写得稍微文艺一点的,但是停课这么久已经退化到不会写文章了. 毕竟省选了我才来补的,时间也过去很久了. 毕竟这次我没有获得任何奖项. 我唯一的获得就是-- --好好看 ...

  7. anyRTC SDK 5月迭代:优化自定义加密功能,让通信更安全

    anyRTC SDK 5月上新,新增多种加密类型,让实时音视频通信更安全:新增移动端推流支持1080P分辨率的支持:此外还对事件上报.日志详情.数据统计.网络传输等多项功能进行了优化改进. 以下为更新 ...

  8. Mac卸载软件真不省心啊

    最近看看磁盘觉得有点小, 就整理了一下, 经过一番折腾, 发现MacOS卸载软件可真是不省心啊. 从应用里移到垃圾桶仅仅是第一步, 当然对于不读写任何文件的应用也许就可以了. 咱们看看赶紧卸载一个软件 ...

  9. SpringBoot数据访问之Druid数据源的使用

    数据访问之Druid数据源的使用 说明:该数据源Druid,使用自定义方式实现,后面文章使用start启动器实现,学习思路为主. 为什么要使用数据源: ​ 数据源是提高数据库连接性能的常规手段,数据源 ...

  10. 【GCC编译器】计算支配树信息 Part1 - 求CFG的深度为主搜索树

    深度为主生成树:将图中所有的结点和那些构成深度为主次序的边表示为树的形式,并将其他的边(这些边不是深度为主次序的一部分)用一种有别于树的方式来表示(我们用虚线而不是实线表示它们) 属于深度为主生成树的 ...