目录 1、Spring Security简介 2、Spring Security实现权限 2.1、Spring Security入门 2.1.1、修改pom文件 2.1.2、添加配置类 2.2、用户认证 2.2.1、自定义组件 2.2.2、
目录
spring 是非常流行和成功的 Java 应用开发框架,Spring Security 正是 Spring 家族中的成员。Spring Security 基于 Spring 框架,提供了一套 WEB 应用安全性的完整解决方案。
正如你可能知道的关于安全方面的两个核心功能是“认证”和“授权”,一般来说,Web 应用的安全性包括用户认证(Authentication)和用户授权(Authorization)两个部分,这两点也是 SpringSecurity 重要核心功能。
(1)用户认证指的是:验证某个用户是否为系统中的合法主体,也就是说用户能否访问该系统。用户认证一般要求用户提供用户名和密码,系统通过校验用户名和密码来完成认证过程。
通俗点说就是系统认为用户是否能登录
(2)用户授权指的是验证某个用户是否有权限执行某个操作。在一个系统中,不同用户所具有的权限是不同的。比如对一个文件来说,有的用户只能进行读取,而有的用户可以进行修改。一般来说,系统会为不同的用户分配不同的角色,而每个角色则对应一系列的权限。
通俗点讲就是系统判断用户是否有权限去做某些事情。
要对Web资源进行保护,最好的办法莫过于Filter。要想对方法调用进行保护,最好的办法莫过于aop(面向切面)。而Spring Security进行认证和鉴权的时候,就是利用的一系列的Filter来进行拦截的。
下面是Spring Security过滤器链:
如图所示,一个请求想要访问到api就会从左到右经过蓝线框里的过滤器,其中绿色部分是负责认证的过滤器,蓝色部分是负责异常处理,橙色部分则是负责授权。进过一系列拦截最终访问到我们的API。
这里面我们只需要重点关注两个过滤器即可:
UsernamePassWordAuthenticationFilter负责登录认证,
FilterSecurityInterceptor负责权限授权。
说明:Spring Security的核心逻辑全在这一套过滤器中,过滤器里会调用各种组件完成功能,掌握了这些过滤器和组件你就掌握了Spring Security!这个框架的使用方式就是对这些过滤器和组件进行扩展。
org.springframework.boot spring-boot-starter-security 2.3.6.RELEASE
说明:依赖包(spring-boot-starter-security)导入后,Spring Security就默认提供了许多功能将整个应用给保护了起来:
@Configuration@EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为public class WebSecurityConfig extends WebSecurityConfigurerAdapter {}
随便访问一个我们写好的接口!!
(出现的页面为spring security的默认验证页面),登录的用户名默认为user,密码在项目启动时会在控制台打印,注意每次启动的时候密码都回发生变化!
输入用户名,密码,成功访问到controller方法并返回数据,说明Spring Security默认安全保护生效。
在实际开发中,这些默认的配置是不能满足我们需要的,我们需要扩展Spring Security组件,完成自定义配置,实现我们的项目需求。
用户认证的流程:
以上大部分步骤,spring-security已经给我们完成了,下面是需要我们做的部分:
拓展security用户名密码封装对象User
public class CustomUser extends User { private SysUser sysUser; public CustomUser(SysUser sysUser, Collection extends GrantedAuthority> authorities) { super(sysUser.getUsername(), sysUser.getPassword(), authorities); this.sysUser = sysUser; } public SysUser getSysUser() { return sysUser; } public void setSysUser(SysUser sysUser) { this.sysUser = sysUser; }}
重写通过用户名获取用户信息的方法(userDetailsService中的loadUserByUsername)
这里是通过用户名进行数据库查询
@Servicepublic class UserDetailsServiceImpl implements UserDetailsService { @Autowired private SysUserService sysUserService; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser sysUser= sysUserService.getByUserName(username); if(sysUser==null){ throw new UsernameNotFoundException("该用户名不存在"); } if(sysUser.getStatus().longValue()==0){ throw new RuntimeException("账号已停用!"); } return new CustomUser(sysUser, Collections.emptyList()); }}
重写密码校验规则
这里使用md5加密
@Componentpublic class CustomMd5PasswordEncoder implements PasswordEncoder { public String encode(CharSequence rawPassword) { return DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes()); } public boolean matches(CharSequence rawPassword, String encodedPassword) { return encodedPassword.equals(DigestUtils.md5DigestAsHex(rawPassword.toString().getBytes())); }}
编写登录过滤器
继承UsernamePasswordAuthenticationFilter,对用户名密码进行拦截登录校验
public class TokenLoginFilter extends UsernamePasswordAuthenticationFilter { public TokenLoginFilter(AuthenticationManager authenticationManager) { this.setAuthenticationManager(authenticationManager);//设置校验者 this.setPostOnly(false); //指定登录接口及提交方式,可以指定任意路径 this.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin/system/index/login","POST")); } @Override public Authentication attemptAuthentication(httpservletRequest req, HttpServletResponse res) throws AuthenticationException { try { //通过流的方式将请求的对象封装为指定对象 LoginVo loginVo = new ObjectMapper().readValue(req.getInputStream(), LoginVo.class); //将指定对象的用户名和密码封装为Authentication对象 Authentication authenticationToken = new UsernamePasswordAuthenticationToken(loginVo.getUsername(), loginVo.getPassword()); //调用authenticate方法完成验证 Authentication authenticate = this.getAuthenticationManager().authenticate(authenticationToken); return authenticate; } catch (IOException e) { throw new RuntimeException(e); } } @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication auth) throws IOException, ServletException { //获取验证成功的对象 CustomUser customUser = (CustomUser) auth.getPrincipal(); //生成token String token = JwtHelper.createToken(customUser.getSysUser().getId(), customUser.getSysUser().getUsername()); //以原生的方式返回token Map map = new HashMap<>(); map.put("token", token); ResponseUtil.out(response, Result.success(map)); } @Override protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException, ServletException { if(e.getCause() instanceof RuntimeException) { ResponseUtil.out(response, Result.build(null, 204, e.getMessage())); } else { ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_MOBLE_ERROR)); } }}
编写token解析器(将认证成功对象传至上下文中)
并且将authentication对象保存至SecurityContext上下文中
public class TokenAuthenticationFilter extends OncePerRequestFilter { public TokenAuthenticationFilter() { } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { logger.info("uri:"+request.getRequestURI()); //如果是登录接口,直接放行 if("/admin/system/index/login".equals(request.getRequestURI())) { chain.doFilter(request, response); return; } UsernamePasswordAuthenticationToken authentication = getAuthentication(request);//获取请求的对象封装为spring-security的对象 //如果对象存在 if(null != authentication) { SecurityContextHolder.getContext().setAuthentication(authentication);//将对象存至SecurityContext(上下文均可使用) chain.doFilter(request, response);//对所有资源进行放行 } else { ResponseUtil.out(response, Result.build(null, ResultCodeEnum.LOGIN_MOBLE_ERROR)); } } private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) { // token置于header里 String token = request.getHeader("token"); logger.info("token:"+token); if (!StringUtils.isEmpty(token)) { String useruame = JwtHelper.getUsername(token); logger.info("useruame:"+useruame); if (!StringUtils.isEmpty(useruame)) { return new UsernamePasswordAuthenticationToken(useruame, null, Collections.emptyList()); } } return null; }}
@Configuration@EnableWebSecurity //@EnableWebSecurity是开启SpringSecurity的默认行为public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsService userDetailsService; @Autowired private CustomMd5PasswordEncoder customMd5PasswordEncoder; @Bean @Override protected AuthenticationManager authenticationManager() throws Exception { return super.authenticationManager(); } //由于博主正在写前后端分离的项目,下面有些不是前后端分离的可以不用加 @Override protected void configure(HttpSecurity http) throws Exception { // 这是配置的关键,决定哪些接口开启防护,哪些接口绕过防护 http //关闭csrf跨站请求伪造 .csrf().disable() // 开启跨域以便前端调用接口 .cors().and() .authorizeRequests() // 指定某些接口不需要通过验证即可访问。登陆接口肯定是不需要认证的 .antMatchers("/admin/system/index/login").permitAll() // 这里意思是其它所有接口需要认证才能访问 .anyRequest().authenticated() .and() //TokenAuthenticationFilter放到UsernamePasswordAuthenticationFilter的前面,这样做就是为了除了登录的时候去查询数据库外,其他时候都用token进行认证。 .addFilterBefore(new TokenAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) .addFilter(new TokenLoginFilter(authenticationManager())); //禁用session http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // 指定UserDetailService和加密器 auth.userDetailsService(userDetailsService) .passwordEncoder(customMd5PasswordEncoder); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/favicon.ico","/swagger-resources @ExceptionHandler(AccessDeniedException.class) public Result error(AccessDeniedException e) throws AccessDeniedException { return Result.build(null).code(204).message("没有权限访问"); }
来源地址:https://blog.csdn.net/qq_61544409/article/details/129685347
--结束END--
本文标题: SpringSecurity权限控制
本文链接: https://www.lsjlt.com/news/395346.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
下载Word文档到电脑,方便收藏和打印~
2024-04-01
2024-04-03
2024-04-03
2024-01-21
2024-01-21
2024-01-21
2024-01-21
2023-12-23
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0