spring security入门demo
一、前言
因项目需要引入spring security权限框架,而之前也没接触过这个一门,于是就花了点时间弄了个小demo出来,说实话,刚开始接触这个确实有点懵,看网上资料写的权限大都是静态,即就是在配置文件或代码里面写定角色,不能动态更改,个人感觉这样实际场景应该应用的不多,于是就进一步研究,整理出了一个可以动态管理个人权限角色demo,其中可能有很多不足或之处,还望指正。本文通过spring boot集成spring security,处理方式没有使用xml文件格式,而是用了注解。
二、表结构
接触过权限这块的,大都应该知道,最核心的有三张表(当然,如果牵涉业务复杂,可能不止)。
一、用户表
二、角色表
三、菜单表(即权限表)
剩余还有两张多对多的表。即用户与角色,角色与菜单。如下图
三、spring security入口
由于本文只是着重说spring security,关于spring boot一块内容会直接带过。如spring boot启动类配置等。
首先会自定义一个类去实现WebSecurityConfigurerAdapter类。重写其中几个方法,代码如下
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired
@Qualifier(value = "userDetailServiceImpl")
private UserDetailsService userDetailsService; @Autowired
private LoginSuccessAuthenticationHandler successAuthenticationHandler; @Autowired
private LoginFailureAuthenticationHandler failureAuthenticationHandler; @Autowired
private AuthenticationAccessDeniedHandler accessDeniedHandler; @Autowired
private UrlAccessDecisionManager decisionManager; @Autowired
private UrlPathFilterInvocationSecurityMetadataSource urlPathFilterInvocationSecurityMetadataSource; @Autowired
private AuthenticationProvider authenticationProvider; @Autowired
private PasswordEncoder passwordEncoder; @Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder);
auth.authenticationProvider(authenticationProvider);
} @Override
public void configure(WebSecurity web) {
web.ignoring().antMatchers("/index.html","/favicon.ico");
} @Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
o.setAccessDecisionManager(decisionManager);
o.setSecurityMetadataSource(urlPathFilterInvocationSecurityMetadataSource);
return o;
}
}) .anyRequest()
.authenticated()// 其他 url 需要身份认证 .and()
.formLogin() //开启登录,如果不指定登录路径(即输入用户名和密码表单提交的路径),则会默认为spring securtiy的内部定义的路径
.successHandler(successAuthenticationHandler)
.failureHandler(failureAuthenticationHandler)// 遇到用户名或密码不正确/用户被锁定等情况异常,会交给此handler处理
.permitAll() .and()
.logout()
.logoutUrl("/logout")//退出操作,其实也有一个handler,如果没其他业务逻辑,可以默认为spring security的handler
.permitAll()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler);
}
在这里会介绍以下几个类作用
一、UserDetailsService
二、AuthenticationProvider
三、AuthenticationAccessDeniedHandler
四、UrlAccessDecisionManager
五、UrlPathFilterInvocationSecurityMetadataSource
至于LoginSuccessAuthenticationHandler、LoginFailureAuthenticationHandler就是用来处理登录成功和登录失败情况,这里不做介绍
3.1、UserDetailService的作用
这个一个接口,通常我们需要去实现它,作用主要是用来我们和数据库做交互用的。简单来说,就是用户名传过来,这个类负责校验用户名是否存在等业务逻辑。
@Component
public class UserDetailServiceImpl implements UserDetailsService { @Autowired
private SysUserDAO userDAO; @Autowired
private PasswordEncoder passwordEncoder; @Override
public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
SysUser sysUser = userDAO.findByUsername(s);
if (sysUser == null){
throw new UsernameNotFoundException("用户不存在");
}
String pwd = passwordEncoder.encode(sysUser.getPassword());
System.out.println(pwd);
return new User(sysUser.getUsername(),pwd,getRoles(sysUser.getRoles()));
} private Collection<GrantedAuthority> getRoles(List<SysRole> roles){
List<GrantedAuthority> list = new ArrayList<>();
for (SysRole role : roles){
SimpleGrantedAuthority grantedAuthority = new SimpleGrantedAuthority(role.getRoleName());
list.add(grantedAuthority);
}
return list;
}
}
代码比较简单,值得注意的是sercurity里的User对象,它的一个构造函数有是哪个参数值,第一个和第二个是用户名和密码,密码作用就是后面用来校验前端传过来的密码正确性。稍后会讲到。至于第三个参数就是当前用户所拥有的角色,作用就是在当前端请求一个接口的时候,会判断这个接口所拥有的权限和该用户所有的权限有重合,简单来说就是该用户是否拥有该接口权限。这里也就实现了一个角色可以动态修改的功能。因其实从数据库查询出来。
3.2、AuthenticationProvider
它也是一个接口,它的作用是用来校验用户密码等功能,当然如短信验证或要第三方验证,也可以实现这个接口,在本文中是用密码校验。前面也说到userDetailService会传一个用户的基本信息。它的主要作用就是为该接口服务的。
@Component
public class LoginAuthenticationProvider implements AuthenticationProvider { @Autowired
private UserDetailsService userDetailsService; @Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
// 获取表单用户名
String username = (String) authentication.getPrincipal();
// 获取表单用户填写的密码
String password = (String) authentication.getCredentials(); UserDetails userDetails = userDetailsService.loadUserByUsername(username); String password1 = userDetails.getPassword();
if (!Objects.equals(password,password1)){
throw new BadCredentialsException("用户名或密码不正确");
} return new UsernamePasswordAuthenticationToken(username,password,userDetails.getAuthorities());
} @Override
public boolean supports(Class<?> aClass) {
return true;
}
}
值得注意的是如果验证通过会返回一个UsernamePasswordAuthenticationToken对象,它的作用就是标志着此用户已通过登录验证,如果没通过,则spring security会捕捉如代码18行的异常,然后再包装一个匿名的token,即AnonymousAuthenticationToken,此token即代表用户未登录。两个接口主要服务于用户登录这块。接下来的三个是服务于权限校验。即接口验证
3.3、UrlPathFilterInvocationSecurityMetadataSource
它的作用是用来处理当前用户是否拥有此接口的权限。
@Component
public class UrlPathFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { @Autowired
private SysMenuDAO sysMenuDAO; private AntPathMatcher antPathMatcher = new AntPathMatcher(); @Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
FilterInvocation filterInvocation = (FilterInvocation) object;
String requestUrl = filterInvocation.getRequestUrl();
// 因为菜单一般随着开发完成,变动不大,此处可以使用缓存,这里为了演示,就直接查库,菜单对应角色需要动态情缓存,如变更菜单和角色关系,需清除缓存
List<SysMenu> all = sysMenuDAO.findAll();
for (SysMenu menu : all) {
if (menu.getRoles().size() != 0 && antPathMatcher.match(menu.getUrlPath(), requestUrl)) {
List<SysRole> roles = menu.getRoles();
int size = roles.size();
String[] values = new String[size];
for (int i = 0; i < size; i++) {
values[i] = roles.get(i).getRoleName();
}
return SecurityConfig.createList(values);
}
}
return SecurityConfig.createList("ROLE_LOGIN");
} @Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
} @Override
public boolean supports(Class<?> clazz) {
return true;
}
}
从代码就可以看出16行的for循环就是获取当前请求接口锁需要的权限,这里使用spring security的路径匹配类。如果该接口·没有权限,这里返回一个标志如ROLE_LOGIN,当然如果需要其他标志可以自行定义,这里为了简便,就用了这个。
3.4、UrlAccessDecisionManager
这个类就是最终的决策类。从3.1到3.2,大家都清楚,已有的信息,用户所有的权限这个已经获取到了,3.3可知当前请求接口的权限也已经获取到了,剩下的肯定就是比较两这个权限集合有没有交集,如果有则表明当前用户拥有此接口的权限。
@Component
public class UrlAccessDecisionManager implements AccessDecisionManager { /**
*
* @param authentication 当前用户信息,和当前用户的拥有权限信息,即来自于userDetailService里的
* @param object 即FilterInvocation对象,可以获取httpServletRequest请求对象
* @param configAttributes 本次访问所需要的权限
* @throws AccessDeniedException
* @throws InsufficientAuthenticationException
*/
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
Iterator<ConfigAttribute> iterator = configAttributes.iterator();
while (iterator.hasNext()) {
ConfigAttribute ca = iterator.next();
//当前请求需要的权限
String needRole = ca.getAttribute();
if ("ROLE_LOGIN".equals(needRole)) {
// 即匿名用户/未登录,如果用户登录成功。那么authententication就是前面提到的UsernamePasswordAuthententicationToken类
if (authentication instanceof AnonymousAuthenticationToken) {
throw new BadCredentialsException("未登录");
} else {// 登录但不具有此路径权限,即前面3.3提到的ROLE_LOGIN,接口没有角色对应,主要用户已经登录成功
break;
}
}
//当前用户所具有的权限
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
throw new AccessDeniedException("权限不足!");
} @Override
public boolean supports(ConfigAttribute attribute) {
return true;
} @Override
public boolean supports(Class<?> clazz) {
return true;
}
}
3.5、AuthenticationAccessDeniedHandler
这个类就是用来接收上面抛出的accessDeniedException异常,
@Component
public class AuthenticationAccessDeniedHandler implements AccessDeniedHandler { @Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
httpServletResponse.setContentType("application/json;charset=UTF-8");
PrintWriter writer = httpServletResponse.getWriter(); writer.print("权限不足");
writer.flush();
}
}
至于哪种异常由哪个类处理,如果了解源码的都知道spring security有一个异常处理过滤器,名字为ExceptionTranslationFilter,要想进一步了解的,可自行看源码,这里提供一个个人认为写的挺好的博文,链接地址,这里不多说废话。
相信大家看完以上文章,对spring security应该有一个大致的了解,,这里附上一个spring security请求经过的过滤器Filter,
执行顺序从上到下。要想研究一波,大家可以先从DelegatingFilterProxy类及它的父类开始入手,一步一步debug下去,相信会有收获的。关于WebSecurityConfig 的配置情况,这里也不多说,网上文章也挺多的。在这里说下当初遇到的一个比较坑的坑
四、遇到的坑
当时场景是这样的,因为项目采用的是前后端分离模式开发的,后端写完代码需要部署到测试服务器,供前端使用,采用的域名是https模式,使用了nginx代码模式,部署上去后。因为登录失败后,spring security会请求到你指定的一个路径,但此时问题出现了,代码部署上去了,测试了一个用户名和密码不正确的情况,结果发现跳转后的host由https变成了http,例子:本来是请求https://abc.com/doLogin路径,但是变成了htttp://abc.com/doLogin。这肯定是访问不了,当时就有点懵了,后面经过分析发现,更改Nginx配置可以达到指定效果,在指定的location加入proxy_set_header X-Forwarded-Proto https,但是这样局限性也有,这样做只能使用https进行访问,所以就没采用,后来就直接百度,百度了的结果大都是更改spring mvc 内部视图解析器配置,如下面
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/" />
<property name="suffix" value=".jsp" />
<!-- 重点是下面配置,将其改为false -->
<property name="redirectHttp10Compatible" value="false" />
</bean>
不过redirect也提醒了我,这个情况由https 变成http 应该就是redirect搞的鬼。那如果将spring security内部由redirect改成forward呢,那情况又会怎样,紧接着,又去看其源码,最后发现这样一个类LoginUrlAuthenticationEntryPoint负责spring security的重定向和转发情况,在其commence方法内进行操作,最后那肯定得试试,最后将该类的useForward属性设置成了true,然后就完美解决。
--------------------------------------------------------------------------------------------------------------------------------------------------分界线--------------------------------------------------------------------------------------
以上就是全部内容,若有不足之处,还望指正,另外附上本文代码地址供大家参考 spring security demo
spring security入门demo的更多相关文章
- SpringBoot集成Spring Security入门体验
一.前言 Spring Security 和 Apache Shiro 都是安全框架,为Java应用程序提供身份认证和授权. 二者区别 Spring Security:重量级安全框架 Apache S ...
- Spring Security OAuth2 Demo
Spring Security OAuth2 Demo 项目使用的是MySql存储, 需要先创建以下表结构: CREATE SCHEMA IF NOT EXISTS `alan-oauth` DEFA ...
- Spring Security OAuth2 Demo —— 隐式授权模式(Implicit)
本文可以转载,但请注明出处https://www.cnblogs.com/hellxz/p/oauth2_impilit_pattern.html 写在前面 在文章OAuth 2.0 概念及授权流程梳 ...
- Spring Security 入门(基本使用)
Spring Security 入门(基本使用) 这几天看了下b站关于 spring security 的学习视频,不得不说 spring security 有点复杂,脑袋有点懵懵的,在此整理下学习内 ...
- Spring Security 入门
一.Spring Security简介 Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spring应用上下文中配 ...
- Spring Security 入门—内存用户验证
简介 作为 Spring 全家桶组件之一,Spring Security 是一个提供安全机制的组件,它主要解决两个问题: 认证:验证用户名和密码: 授权:对于不同的 URL 权限不一样,只有当认证的用 ...
- Spring Security 入门(1-1)Spring Security是什么?
1.Spring Security是什么? Spring Security 是一个安全框架,前身是 Acegi Security , 能够为 Spring企业应用系统提供声明式的安全访问控制. Spr ...
- Spring Security OAuth2 Demo -- good
1. 添加依赖授权服务是基于Spring Security的,因此需要在项目中引入两个依赖: <dependency> <groupId>org.springframework ...
- SpringBoot整合Spring Security使用Demo
https://start.spring.io/ 生成SpringBoot项目 pom文件应该是我这样的: <?xml version="1.0" encoding=&quo ...
随机推荐
- Confluence 6 创建一个主题
如果你希望创建你自己的主题,你需要写一个 Confluence 插件.请参考我们开发文档中的下面页面 开始使用 插件开发. 请参考开发者指南的页面来 写一个 Confluence 主题. 使用 主题插 ...
- poj1236 SCC+缩点
/* 强连通分量内的点可以互相传送,可以直接缩点 缩点后得到一棵树 第一问的答案是零入度点数量, 第二问: 加多少边后变成强连通图 树上入度为0的点有p个,出度为0的点为q,那么答案就是max(p,q ...
- vue 的router的简易运用
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Python笔记记录
python2和python3的不同: Unicode(统一码.万国码),在3里面可以直接写中文了. python2里rae_input与python3中的input效果一样 在计算机内存中,统一用U ...
- RabbitMQ疑惑释义
MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们.消息传递指的是程序之间 ...
- javascript 将毫秒值转换为天-小时-分钟-秒钟
var start_timeMS = new Date(start_date).getTime(); var end_timeMS = new Date(end_date).getTime(); va ...
- 让Mysql支持Emoji表情,解决[Err] 1366 - Incorrect string value: '\xF0\xA3\x84\x83'
mysql insert内容包含表情或者unicode码时候,插入Mysql时失败了,报如下异常: java.sql.SQLException: Incorrect string value: '\x ...
- 一 time与datetime模块
时间戳(timestamp):通常来说,时间戳表示的是从1970年1月1日00:00:00开始按秒计算的偏移量.我们运行“type(time.time())”,返回的是float类型. 格式化的时间字 ...
- 页面布局之--Font Awesome+导航
页面布局之--Font Awesome+导航 Font Awesome为您提供可缩放的矢量图标,您可以使用CSS所提供的所有特性对它们进行更改,包括:大小.颜色.阴影或者其它任何支持的效果. 下载地址 ...
- Caffe使用新版本CUDA和CuDNN
因为一些原因还是需要使用别人基于Caffe的代码,但是代码比较老,默认不支持高版本的cuda或者cudnn 怎么办呢?基本上就是把最新官方Caffe-BVLC的几个关键文件拿过来替换即可. 脚本如下: ...