本人最近接受的新任务是从零搭一个管理系统的权限管理模块,从零开始学习了spring security,已完成绝大部分设计和开发,和大家分享一些学习和开发心得.

首先是数据库的设计,这里是

按照产品的需求,本权限管理模块,权限同时与角色和用户关联,要求角色有普通用户和管理员,用户和权限之间做直接的关联,我采用的是在权限校验时,先查询是否是管理员,如果不是则查询"用户--权限表".

接下来用Spring Boot配置Spring Security.

自定义一个SecurityConfig实现WebSecurityConfigurerAdapter,做权限核心控制类

@Configuration
@EnableWebSecurity //启用web权限
@EnableGlobalMethodSecurity(prePostEnabled = true) //启用方法验证
public class SecurityConfig extends WebSecurityConfigurerAdapter {
// @Autowired
// private CasProperties casProperties;
@Autowired
CustomUserDetailsService customUserDetailsService; @Autowired
MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;
@Autowired
MyAccessDecisionManager myAccessDecisionManager;
@Autowired
AuthenticationAccessDeniedHandler authenticationAccessDeniedHandler;
@Autowired
CustomizeAuthenticationEntryPoint authenticationEntryPoint;
@Autowired
CustomizeAuthenticationFailureHandler authenticationFailureHandler;
@Autowired
CustomizeSessionInformationExpiredStrategy sessionInformationExpiredStrategy; /**定义认证用户信息获取来源,密码校验规则等*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
super.configure(auth); } /**
* 定义安全策略
* @param http
* @throws Exception
*/
@Override
protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests()//配置安全策略
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
o.setAccessDecisionManager(myAccessDecisionManager);
return o;
}
})
.anyRequest().authenticated()//其余的所有请求都需要验证
.and()
.logout()
.permitAll()//定义logout不需要验证
.and()
//把自定义的验证码过滤器添加到验证用户名密码的过滤器前
// .addFilterBefore(new VerifyFilter(), UsernamePasswordAuthenticationFilter.class)
.formLogin()
// .loginProcessingUrl("/login")
.loginProcessingUrl(UrlConstant.LOGIN_URL)
.usernameParameter("userId").passwordParameter("password")
.defaultSuccessUrl(UrlConstant.DEFAULT_SUCCESS_Url)
.permitAll().//允许所有用户
failureHandler(authenticationFailureHandler);
//登录失败处理逻辑
//异常处理(权限拒绝、登录失效等)
http.exceptionHandling().
authenticationEntryPoint(authenticationEntryPoint)//匿名用户访问无权限资源时的异常处理and()
.and()
.cors()//新加入
.and()
.csrf().disable();
http.cors()
.and()
.exceptionHandling().accessDeniedHandler(authenticationAccessDeniedHandler)
//会话管理
.and().sessionManagement().
maximumSessions(3).//同一账号同时登录最大用户数
expiredSessionStrategy(sessionInformationExpiredStrategy);//会话信息过期策略会话信息过期策略(账号被挤下线)
// http.csrf().disable();//关闭CSRF保护
} /**
* 默认的账号登录认证策略
* @return
*/
@Bean
public AbstractUserDetailsAuthenticationProvider usernameAndPasswordAuthenticationProvider(){
DaoAuthenticationProvider daoAuthenticationProvider = new DaoAuthenticationProvider();
daoAuthenticationProvider.setUserDetailsService( accountDetailsService() );
daoAuthenticationProvider.setPasswordEncoder( bCryptPasswordEncoder() );//设置密码策略
return daoAuthenticationProvider;
} /**
* 注册获取用户权限Bean
* @return
*/
@Bean
public CustomUserDetailsService accountDetailsService(){
return new CustomUserDetailsService();
}
/**
* 注册md5加密Bean
* @return
*/
@Bean
public MessageDigestPasswordEncoder bCryptPasswordEncoder(){
return new MessageDigestPasswordEncoder("MD5");
}
} 在这个类中configure(HttpSecurity http)方法是最核心的方法,我在这个方法中配置了,登录访问的路径
登录访问的路径
.loginProcessingUrl(UrlConstant.LOGIN_URL)
,登录成功的调用的方法
.defaultSuccessUrl(UrlConstant.DEFAULT_SUCCESS_Url)
,失败调用的方法
failureHandler(authenticationFailureHandler);
,无权限时调用的方法
.exceptionHandling().accessDeniedHandler(authenticationAccessDeniedHandler)
匿名用户访问无权限资源时的异常处理
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint)
//会话管理
.and().sessionManagement().
maximumSessions(3).//同一账号同时登录最大用户数
expiredSessionStrategy(sessionInformationExpiredStrategy);//会话信息过期策略会话信息过期策略(账号被挤下线)
接着配置其他的处理类:
/**
* 拒绝访问处理
*/
@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();
}
}
/**
* @Description: 匿名用户访问无权限资源时的异常
* @Date Create in 2019/9/3 21:35
*/
@Component
public class CustomizeAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
JsonResult result = ResultTool.fail(ResultCode.USER_NOT_LOGIN);
httpServletResponse.setContentType("text/json;charset=utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}
/**
* @Author: cg
* @Description: 登录失败处理逻辑
*/
@Component
public class CustomizeAuthenticationFailureHandler implements AuthenticationFailureHandler { @Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException{
//返回json数据
JsonResult result = null;
if (e instanceof VerifyCodeException){
result = ResultTool.fail(ResultCode.USER_VERIFICATION_CODE_ERROR);
}else if (e instanceof AccountExpiredException) {
//账号过期
result = ResultTool.fail(ResultCode.USER_ACCOUNT_EXPIRED);
} else if (e instanceof BadCredentialsException) {
//密码错误
result = ResultTool.fail(ResultCode.USER_CREDENTIALS_ERROR);
} else if (e instanceof CredentialsExpiredException) {
//密码过期
result = ResultTool.fail(ResultCode.USER_CREDENTIALS_EXPIRED);
} else if (e instanceof DisabledException) {
//账号不可用
result = ResultTool.fail(ResultCode.USER_ACCOUNT_DISABLE);
} else if (e instanceof LockedException) {
//账号锁定
result = ResultTool.fail(ResultCode.USER_ACCOUNT_LOCKED);
} else if (e instanceof InternalAuthenticationServiceException) {
//用户不存在
result = ResultTool.fail(ResultCode.USER_ACCOUNT_NOT_EXIST);
}else{
//其他错误
result = ResultTool.fail(ResultCode.COMMON_FAIL);
}
//处理编码方式,防止中文乱码的情况
httpServletResponse.setContentType("text/json;charset=utf-8");
//塞到HttpServletResponse中返回给前台
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}
/**
* @Author: cg
* @Description: 会话信息过期策略
*/
@Component
public class CustomizeSessionInformationExpiredStrategy implements SessionInformationExpiredStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent sessionInformationExpiredEvent) throws IOException, ServletException {
JsonResult result = ResultTool.fail(ResultCode.USER_ACCOUNT_USE_BY_OTHERS);
HttpServletResponse httpServletResponse = sessionInformationExpiredEvent.getResponse();
httpServletResponse.setContentType("text/json;charset=utf-8");
httpServletResponse.getWriter().write(JSON.toJSONString(result));
}
}
MyAccessDecisionManager 是认证核心处理类,校验访问的接口所对应的权限,登录的用户是否拥有
/**
* 认证核心类
*/
@Service
public class MyAccessDecisionManager implements AccessDecisionManager {
/**
* decide 方法是判定是否拥有权限的决策方法,authentication是CustomUserService
* 中循环添加到 GrantedAuthority 对象中的权限信息集合,object 包含客户端发起的请求的requset信息,
* 可转换为 HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
* configAttributes为MyFilterInvocationSecurityMetadataSource的getAttributes(Object object)
* 这个方法返回的结果.
*
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException { if(null== configAttributes || configAttributes.size() <=0) {
return;
}
ConfigAttribute c;
String needRole;
//遍历用户已有的权限中是否有这一个接口所对应的的权限
for(Iterator<ConfigAttribute> iter = configAttributes.iterator(); iter.hasNext(); ) {
c = iter.next();
needRole = c.getAttribute();
for(GrantedAuthority ga : authentication.getAuthorities()) {//authentication 为在注释1 中循环添加到 GrantedAuthority 对象中的权限信息集合
//比较是否是该接口所需的权限
if(needRole.trim().equals(ga.getAuthority())) {
return;
}
}
}
throw new AccessDeniedException("no right");
} @Override
public boolean supports(ConfigAttribute attribute) {
return true;
} @Override
public boolean supports(Class<?> clazz) {
return true;
}
}
我通过MyFilterInvocationSecurityMetadataSource加载url权限表,把每一个接口对应的权限存储到数据库,在服务器启动时调用,
把url和对应的权限加载到内存中,在用户每一次访问被 接口时校验该接口是否被存到数据库中,如果被存储到了数据库就得到其对应的权限,
如果数据库中没有这个url的地址,则放行不进行拦截
/**
* 加载权限表及判断url权限类
*/
@Service
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Autowired
PermissionMapper permissionMapper; private HashMap<String, Collection<ConfigAttribute>> map = null; /**
* 加载权限表中所有权限
*/
public void loadResourceDefine() {
map = new HashMap<String, Collection<ConfigAttribute>>();
//获取url_auth表中所有的url和权限名
List<AuthUrlPermission> authUrlPermissions = permissionMapper.selectUrlPerm();
//把所有的url和对应的权限名添加到内存的map中,用于后面的权限对比
for (AuthUrlPermission permission : authUrlPermissions) {
ConfigAttribute cfg = new SecurityConfig(permission.getAuthName());
List<ConfigAttribute> list = new ArrayList<>();
list.add(cfg);
//用于restful风格的接口地址和请求方法拼接起来存入内存的map中
map.put(permission.getUrlName()+":"+permission.getMethod(), list);
} } /**
* 此方法是为了判定用户请求的url 是否在权限表中,如果在权限表中,则返回给 decide 方法, 用来判定用户
* 是否有此权限。如果不在权限表中则放行。
*/
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
//如果内存中存储url和权限的map为null则去调用加载map方法
if (map == null) {
loadResourceDefine();
}
// object 中包含用户请求的request的信息
HttpServletRequest request = ((FilterInvocation) object).getHttpRequest();
for (Map.Entry<String, Collection<ConfigAttribute>> entry : map.entrySet()) {
String urlAndMethod = entry.getKey();
//把key中的url和请求方法通过:切割开
String url = urlAndMethod.split(":")[0];
String method = urlAndMethod.split(":")[1];
//验证url和请求方法与请求中的是否一致
if (new AntPathRequestMatcher(url).matches(request) &&
request.getMethod().equals(method)) {
//如果url和请求方法与请求中的一致.则返回该请求对应的权限名
return map.get(urlAndMethod);
}
}
//如果请求的地址和方法不在存储的url表中则不做拦截
return null;
} @Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
} @Override
public boolean supports(Class<?> clazz) {
return true;
}
} 从数据库获取用户信息和用户所拥有的的权限,提供给AuthenticationManager进行登录校验
/**
* 从数据库获取用户权限类
*/
@Service("customUserDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
@Autowired
PermissionMapper permissionMapper;
@Autowired
UserMapper userMapper; @Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
System.out.println("当前的用户名是:"+username);
//根据用户名从数据库用户所拥有的权限
AuthUser authUser2 = userMapper.userLogin(username);
if (authUser2 == null){
throw new UsernameNotFoundException("用户名不存在");
}
Set<GrantedAuthority> authorities = new HashSet<>();
List<PermissionCheckVO> permissionCheckVOS = permissionMapper.selectCheckPerm(username);
//添加用户默认开启的权限
authorities.add(new SimpleGrantedAuthority("USER"));
authorities.add(new SimpleGrantedAuthority("processList"));
authorities.add(new SimpleGrantedAuthority("processList_edit"));
authorities.add(new SimpleGrantedAuthority("process_approval_child"));
authorities.add(new SimpleGrantedAuthority("process_approval_child_edit"));
//遍历添加权限
for (PermissionCheckVO r: permissionCheckVOS) {
//添加编辑权限
if (r.getEditStatus()!=null && r.getEditStatus().equals(true)){
authorities.add(new SimpleGrantedAuthority(r.getAuthName()));
r.setAuthName(r.getAuthName()+"_edit");
}
authorities.add(new SimpleGrantedAuthority(r.getAuthName()));
}
//把数据库中的用户名和密码以及权限返回给AuthenticationManager调用
User user = new User(username, authUser2.getPassword(), authorities);
return user;
}
}
以上是我的springboot+spring security的配置
 

spring boot+spring security 使用随笔的更多相关文章

  1. Spring boot +Spring Security + Thymeleaf 认证失败返回错误信息

    [Please make sure to select the branch corresponding to the version of Thymeleaf you are using] Stat ...

  2. 255.Spring Boot+Spring Security:使用md5加密

    说明 (1)JDK版本:1.8 (2)Spring Boot 2.0.6 (3)Spring Security 5.0.9 (4)Spring Data JPA 2.0.11.RELEASE (5)h ...

  3. 256.Spring Boot+Spring Security: MD5是加密算法吗?

    说明 (1)JDK版本:1.8 (2)Spring Boot 2.0.6 (3)Spring Security 5.0.9 (4)Spring Data JPA 2.0.11.RELEASE (5)h ...

  4. Spring Boot+Spring Security:获取用户信息和session并发控制

    说明 (1)JDK版本:1.8(2)Spring Boot 2.0.6(3)Spring Security 5.0.9(4)Spring Data JPA 2.0.11.RELEASE(5)hiber ...

  5. [权限管理系统(四)]-spring boot +spring security短信认证+redis整合

    [权限管理系统]spring boot +spring security短信认证+redis整合   现在主流的登录方式主要有 3 种:账号密码登录.短信验证码登录和第三方授权登录,前面一节Sprin ...

  6. 基于Spring Boot+Spring Security+JWT+Vue前后端分离的开源项目

    一.前言 最近整合Spring Boot+Spring Security+JWT+Vue 完成了一套前后端分离的基础项目,这里把它开源出来分享给有需要的小伙伴们 功能很简单,单点登录,前后端动态权限配 ...

  7. 快速搭建基于Spring Boot + Spring Security 环境

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 1.Spring Security 权限管理框架介绍 简介: Spring Security 提供了基于 ...

  8. Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(二)

    Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(二) 摘要 上一篇https://javaymw.com/post/59我们已经实现了基本的登录和t ...

  9. Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(一)

    标题 Spring Boot+Spring Security+JWT 实现 RESTful Api 认证(一) 技术 Spring Boot 2.Spring Security 5.JWT 运行环境 ...

  10. spring Boot+spring Cloud实现微服务详细教程第二篇

    上一篇文章已经说明了一下,关于spring boot创建maven项目的简单步骤,相信很多熟悉Maven+Eclipse作为开发常用工具的朋友们都一目了然,这篇文章主要讲解一下,构建spring bo ...

随机推荐

  1. vue-lazyload: 想弃坑,但没有找到合适的替代品

    vue-lazyload,相信在vue项目中大家都有用到过它,同时也遇到过大大小小的坑.笔者也遇到过这样一个bug,在一个图片列表页面中,总有一定的概率图片的状态为load,导致图片一直加载中...这 ...

  2. win10 uwp 商业游戏 1.1.5

    本文是在win10 uwp 商业游戏 基础上继续开发,添加一些无聊的游戏 因为在发布几个月,下载量很少,小伙伴说游戏就玩不到几分钟就不想玩,于是我就想加入其他游戏 下面我来告诉大家如何在游戏中添加多个 ...

  3. 关于methods、computed、watch的使用

    关于methods.computed.watch的使用,前前后后我有转载过好几篇别人的文章.但始终没有自己成型的博文来记录,现自己尝试性的总结一下三者之间的区别: computed:计算属性 comp ...

  4. sort排序,按指定字段进去重,sort -t "^" -k 8 -su,ls给文件名中数字排序sort -k1.5n,Tab符要转义

    sort sort 命令对 File 参数指定的文件中的行排序,并将结果写到标准输出.如果 File 参数指定多个文件,那么 sort 命令将这些文件连接起来,并当作一个文件进行排序. sort语法 ...

  5. CP策略含有中文字符提交失败故障解决

    硬件平台:CP5600 系统版本:R80.10 补丁版本:TAKE103 故障现象:提交新增策略失败,日志显示 if the problem persists contact Checkpoint S ...

  6. python代码规范以及函数注释规范

    摘要 本文给出主Python版本标准库的编码约定.CPython的C代码风格参见​PEP7.本文和​PEP 257 文档字符串标准改编自Guido最初的<Python Style Guide&g ...

  7. Visio模具与模版

    yy 这些形状就是模具 点击更多形状 然后新建模具 也可以打开已有的模具 模具名字右边有星号 代表模具未保存 鼠标右键单击可以选择保存 选择属性可以进行设置(信息之类的)也可以进行关闭 模具文件三种打 ...

  8. 第二阶段:4.商业需求文档MRD:3.PRD-页面结构图

    这也是功能结构以及优先级 这是页面层级 页面结构图 再细分某一个频道或者子页面 层层细分 用mind做的页面结构图 里面也包含了功能

  9. 聊聊多线程哪一些事儿(task)之 二 延续操作

    hello,又见面啦,昨天我们简单的介绍了如何去创建和运行一个task.如何实现task的同步执行.如何阻塞等待task集合的执行完毕等待,昨天讲的是task的最基本的知识点,如果你没有看昨天的博客, ...

  10. 使用element的upload组件实现一个完整的文件上传功能(下)

    本篇文章是<使用element的upload组件实现一个完整的文件上传功能(上)>的续篇. 话不多说,接着上一篇直接开始 一.功能完善—保存表格中每一列的文件列表状态 1.思路 保存表格中 ...