赞
踩
转载自:www.javaman.cn
添加pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
认证管理
流程图解读:
1、用户提交用户名、密码被SecurityFilterChain中的 UsernamePasswordAuthenticationFilter 过滤器获取到, 封装为请求Authentication,通常情况下是UsernamePasswordAuthenticationToken这个实现类。
2、然后过滤器将Authentication提交至认证管理器(AuthenticationManager)进行认证 。
3、认证成功后, AuthenticationManager 身份管理器返回一个被填充满了信息的(包括上面提到的权限信息, 身份信息,细节信息,但密码通常会被移除) Authentication 实例。
4、SecurityContextHolder 安全上下文容器将第3步填充了信息的 Authentication ,通过 SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。 可以看出AuthenticationManager接口(认证管理器)是认证相关的核心接口,也是发起认证的出发点,它 的实现类为ProviderManager。而Spring Security支持多种认证方式,因此ProviderManager维护着一个 List 列表,存放多种认证方式,最终实际的认证工作是由 AuthenticationProvider完成的。咱们知道web表单的对应的AuthenticationProvider实现类为 DaoAuthenticationProvider,它的内部又维护着一个UserDetailsService负责UserDetails的获取。最终 AuthenticationProvider将UserDetails填充至Authentication。
授权管理
访问资源(即授权管理),访问url时,会通过FilterSecurityInterceptor拦截器拦截,其中会调用SecurityMetadataSource的方法来获取被拦截url所需的全部权限,再调用授权管理器AccessDecisionManager,这个授权管理器会通过spring的全局缓存SecurityContextHolder获取用户的权限信息,还会获取被拦截的url和被拦截url所需的全部权限,然后根据所配的投票策略(有:一票决定,一票否定,少数服从多数等),如果权限足够,则决策通过,返回访问资源,请求放行,否则跳转到403页面、自定义页面。
根据上面的认证授权流程,具体的实现步骤从3-8
1、首先定义一个我们自己的实现类集成SpringSecurity的UserDetailsService,实现loadUserByUsername方法,就是下面的步骤3,当抛出AccessDeniedException时,就要进行处理,也就是步骤4,
2、接着编写SpringSecurityConfig配置文件,就是下面的步骤7,需要进行认证成功后的处理,就是下面的步骤5
3、认证失败后,对认证失败进行处理,就是下面的步骤6
5、通过auth.userDetailsService(sysUserService),配置
AuthenticationManagerBuilder
来使用sysUserService
加载用户的详细信息,并使用密码编码器来处理密码。这样,当应用程序需要验证用户的身份时,它会使用这些配置来检查用户提供的凭据(通常是用户名和密码)是否正确。如果凭据正确,用户将被允许访问受保护的资源;如果凭据不正确,将拒绝访问6、接下来通过步骤7的安全配置:
定义哪些URL不需要身份验证(如/loginPage和/getImgCode)。
配置登录页面、登录处理URL、成功和失败的处理程序等。
添加一个自定义的验证码过滤器。
配置“记住我”功能。
禁用CSRF保护(通常不推荐这样做,但在这里它被禁用了)。
设置响应头中的X-Frame-Options属性。
配置会话管理,例如定义会话失效时的跳转URL。
定义一个名为SysUserService
的服务类,该类主要用于处理与系统用户相关的业务逻辑:
SysUserService
继承了ServiceImpl
并实现了UserDetailsService
接口,这意味着它提供了与用户详细信息相关的服务。@Autowired
注解注入了多个mapper(数据访问对象)和一个密码编码器。这些mapper可能用于访问数据库中的用户、菜单、用户角色和用户岗位信息。loadUserByUsername
方法根据提供的用户名从数据库中检索用户信息。如果找不到用户,则抛出UsernameNotFoundException
异常。ConfigConsts.ADMIN_USER
定义),则为其分配所有的菜单和角色。AccessDeniedException
异常。User
对象,并返回。为Spring Security框架提供用户的详细信息和权限设置,确保用户在系统中的访问和操作都是基于其分配的权限进行的。
package com.ds.blog.system.service; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.ds.blog.system.entity.SysUser; import com.ds.blog.system.entity.SysUserPost; import com.ds.blog.system.entity.SysUserRole; import com.ds.blog.system.entity.dto.ModifyPassDTO; import com.ds.blog.system.entity.dto.ResetPassDTO; import com.ds.blog.system.entity.dto.UserParamDTO; import com.ds.blog.system.mapper.*; import com.ds.common.constant.ConfigConsts; import com.ds.common.domain.XmSelectNode; import com.ds.common.enums.ResultStatus; import com.ds.core.exception.MyGlobalException; import com.ds.core.util.CommonUtil; import org.springframework.aop.framework.AopContext; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.thymeleaf.util.ArrayUtils; import javax.servlet.http.HttpServletResponse; import java.lang.reflect.Array; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.Optional.ofNullable; @Service public class SysUserService extends ServiceImpl<SysUserMapper, SysUser> implements UserDetailsService { @Autowired private SysUserMapper sysUserMapper; @Autowired private SysMenuMapper sysMenuMapper; @Autowired private SysUserRoleMapper sysUserRoleMapper; @Autowired private SysUserPostMapper sysUserPostMapper; @Autowired private BCryptPasswordEncoder passwordEncoder; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { SysUser user = sysUserMapper.findByUserName(username); if (ObjectUtil.isNull(user)) { throw new UsernameNotFoundException("用户不存在"); } List<String> menuRole; if (ConfigConsts.ADMIN_USER.equals(username)) { menuRole = sysMenuMapper.findMenuRole(); } else { String roleIds = user.getRoleIds(); if (StringUtils.isBlank(roleIds)) { throw new AccessDeniedException("用户未分配菜单"); } Long[] ids = CommonUtil.getId(user.getRoleIds()); menuRole = sysMenuMapper.findMenuRoleByRoleIds(ids); } return new User(user.getUserName(), user.getPassWord(), ConfigConsts.SYS_YES.equals(user.getEnabled()), true, true, true, AuthorityUtils.commaSeparatedStringToAuthorityList(String.join(",", menuRole))); } }
在步骤3中抛出 AccessDeniedException(“用户未分配菜单”),我们需要自定义处理器来处理该异常。
这段代码定义了一个名为CustomAccessDeniedHandler
的类,该类实现了AccessDeniedHandler
接口。这主要是用于Spring Security框架中,当用户尝试访问他们没有权限的资源时,自定义如何处理这种访问被拒绝的情况。
以下是代码的详细解释:
@Component:这是一个Spring的注解,它表示CustomAccessDeniedHandler
是一个Spring组件。这意味着Spring会在启动时自动检测、注册并管理这个类的实例。
实现AccessDeniedHandler接口:AccessDeniedHandler
是Spring Security中的一个接口,它要求实现一个handle
方法。当在Spring Security中发生AccessDeniedException
异常时,这个handle
方法会被调用。
handle方法:
参数
:此方法有三个参数:
HttpServletRequest request
:代表HTTP请求,可以用来获取请求相关的信息,如请求头、请求参数等。HttpServletResponse response
:代表HTTP响应,可以用来设置响应的状态码、响应头、响应体等。AccessDeniedException e
:是触发此处理程序的异常。可以提供有关为什么访问被拒绝的信息。方法体
:
response.setCharacterEncoding("utf-8")
:设置响应的字符编码为UTF-8。response.setContentType("application/json;charset=utf-8")
:设置响应的内容类型为JSON,并确保字符编码为UTF-8。response.getWriter().write(new ObjectMapper().writeValueAsString(new MyGlobalException(ResultStatus.TEST_USER_LIMIT)))
:使用Jackson库的ObjectMapper
将MyGlobalException
对象序列化为JSON字符串,并将其写入响应体。从代码中可以看出,当访问被拒绝时,会返回一个具有特定状态的全局异常信息。总的来说,这段代码的主要作用是为Spring Security提供一个自定义的访问拒绝处理器,当用户尝试访问他们没有权限的资源时,它会返回一个具有特定状态的JSON格式的错误信息。
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e) throws IOException, ServletException {
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(new ObjectMapper().writeValueAsString(e.getMessage()));
}
}
定义一个名为DefaultAuthenticationSuccessHandler
的类,该类是Spring Security框架中用于处理成功认证后的逻辑的组件。以下是代码的主要功能点:
log
。这允许你在类中方便地记录日志。SavedRequestAwareAuthenticationSuccessHandler
,这是Spring Security提供的一个处理器,用于在用户成功认证后重定向他们到最初请求的页面(如果存在的话)。PrintWriter
对象将一个表示成功的JSON字符串写入响应体,并刷新输出流。总的来说,这段代码的主要作用是当用户成功认证后,记录一个日志,并向客户端发送一个表示成功的JSON响应。
@Component
@Slf4j
public class DefaultAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws ServletException, IOException {
log.info("-----login in success----");
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
PrintWriter writer = response.getWriter();
writer.write(JSON.toJSONString(Result.success()));
writer.flush();
}
}
定义一个名为DefaultAuthenticationFailureHandler
的类,该类是Spring Security框架中用于处理认证失败后的逻辑的组件。以下是代码的主要功能点:
log
。这允许你在类中方便地记录日志。SimpleUrlAuthenticationFailureHandler
,这是Spring Security提供的一个处理器,用于处理认证失败的情况,并默认重定向到一个指定的失败URL。PrintWriter
对象将错误信息写入响应体。BadCredentialsException
(通常表示用户名或密码不正确),则返回一个特定的错误消息“用户名或密码错误,请重试。”。总的来说,这段代码的主要作用是当认证失败时,记录一个日志,并根据异常类型向客户端发送一个表示失败的JSON响应,其中包含具体的错误信息。
@Component @Slf4j public class DefaultAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { log.info("login in failure : " + exception.getMessage()); response.setContentType("application/json;charset=utf-8"); response.setCharacterEncoding("utf-8"); PrintWriter writer = response.getWriter(); String message; if (exception instanceof BadCredentialsException) { message = "用户名或密码错误,请重试。"; writer.write(JSON.toJSONString(Result.failure(message))); }else{ writer.write(JSON.toJSONString(Result.failure(exception.getMessage()))); } writer.flush(); } }
这段代码是一个Spring Security的配置类,用于配置Web应用的安全性。Spring Security是一个功能强大且可定制的身份验证和访问控制框架。
以下是该代码的主要功能点:
WebSecurityConfigurerAdapter
,允许你自定义Spring Security的配置。AuthenticationManager
,这是处理身份验证逻辑的核心组件。在这里,它配置了一个UserDetailsService
和一个PasswordEncoder
来处理用户的身份验证。/loginPage
和/getImgCode
)。总的来说,这段代码配置了Spring Security来处理Web应用的安全性,包括身份验证、访问控制、会话管理等。需要注意的是,其中禁用了CSRF保护,这通常是不安全的做法,除非有特定的原因。
package com.ds.core.config; import com.ds.blog.system.service.SysUserService; import com.ds.core.security.CustomAccessDeniedHandler; import com.ds.core.security.DefaultAuthenticationFailureHandler; import com.ds.core.security.DefaultAuthenticationSuccessHandler; import com.ds.core.security.filter.ValidateCodeFilter; import net.bytebuddy.asm.Advice; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; 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.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class MySecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private SysUserService sysUserService; @Autowired private DefaultAuthenticationSuccessHandler defaultAuthenticationSuccessHandler; @Autowired private DefaultAuthenticationFailureHandler defaultAuthenticationFailureHandler; @Autowired private ValidateCodeFilter validateCodeFilter; @Autowired private CustomAccessDeniedHandler accessDeniedHandler; @Bean public BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(sysUserService) .passwordEncoder(passwordEncoder()); } @Override public void configure(WebSecurity web) throws Exception { // 不需要权限能访问的资源 web.ignoring() // 接口放行 .antMatchers("/api/**") .antMatchers("/front/**") // 静态资源 .antMatchers("/static/**") .antMatchers("/favicon.ico"); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // 放过 .antMatchers("/loginPage", "/getImgCode").permitAll() .antMatchers("/**/*.jpg", "/**/*.png", "/**/*.gif", "/**/*.jpeg").permitAll() // 剩下的所有的地址都是需要在认证状态下才可以访问 .anyRequest().authenticated() .and() // 过滤登录验证码 .addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class) // 配置登录功能 .formLogin() .usernameParameter("userName") .passwordParameter("passWord") // 指定指定要的登录页面 .loginPage("/loginPage") // 处理认证路径的请求 .loginProcessingUrl("/login") .successHandler(defaultAuthenticationSuccessHandler) .failureHandler(defaultAuthenticationFailureHandler) .and() .exceptionHandling() .accessDeniedHandler(accessDeniedHandler) .and() // 登出 .logout() .invalidateHttpSession(true) .deleteCookies("remember-me") .logoutUrl("/logout") .logoutSuccessUrl("/loginPage") .and() .rememberMe() // 有效期7天 .tokenValiditySeconds(3600 * 24 * 7) // 开启记住我功能 .rememberMeParameter("remember-me") .and() //禁用csrf .csrf().disable() // header response的X-Frame-Options属性设置为SAMEORIGIN .headers().frameOptions().sameOrigin() .and() // 配置session管理 .sessionManagement() //session失效默认的跳转地址 .invalidSessionUrl("/loginPage"); } }
<!DOCTYPE html> <html lang="zh" xmlns:th="http://www.thymeleaf.org"> <head> <title>ds博客</title> <div th:replace="common/link::header"></div> <link rel="stylesheet" th:href="@{/static/layuiadmin/style/login.css}" media="all"> </head> <body> <div class="layadmin-user-login layadmin-user-display-show" id="LAY-user-login" style="display: none;"> <div class="layadmin-user-login-main"> <div class="layadmin-user-login-box layadmin-user-login-header"> <h2>ds博客</h2> <p>后台登录</p> </div> <div class="layadmin-user-login-box layadmin-user-login-body layui-form"> <div class="layui-form-item"> <label class="layadmin-user-login-icon layui-icon layui-icon-username" for="LAY-user-login-username"></label> <input type="text" name="userName" value="test" id="LAY-user-login-username" lay-verify="required" placeholder="用户名" class="layui-input"> </div> <div class="layui-form-item"> <label class="layadmin-user-login-icon layui-icon layui-icon-password" for="LAY-user-login-password"></label> <input type="password" name="passWord" value="test" id="LAY-user-login-password" lay-verify="required" placeholder="密码" class="layui-input"> </div> <div class="layui-form-item"> <div class="layui-row"> <div class="layui-col-xs7"> <label class="layadmin-user-login-icon layui-icon layui-icon-vercode"></label> <input type="text" name="code" lay-verify="required" placeholder="图形验证码" class="layui-input"> </div> <div class="layui-col-xs5"> <div style="margin-left: 10px;"> <img id="codeImg" class="layadmin-user-login-codeimg"> </div> </div> </div> </div> <div class="layui-form-item" style="margin-bottom: 20px;"> <input type="checkbox" name="remember-me" lay-skin="primary" title="记住密码"> </div> <div class="layui-form-item"> <button class="layui-btn layui-btn-fluid layui-bg-blue" lay-submit lay-filter="login">登 录</button> </div> </div> </div> <div th:replace="common/script::footer"></div> <script th:inline="javascript"> layui.config({ base: '/static/layuiadmin/' //静态资源所在路径 }).extend({ index: 'lib/index' //主入口模块 }).use(['index', 'user'], function(){ let $ = layui.$, form = layui.form; // 初始化 getImgCode(); form.render(); //提交 form.on('submit(login)', function(obj) { // 打开loading let loading = layer.load(0, { shade: false, time: 2 * 1000 }); // 禁止重复点击按钮 $('.layui-btn').attr("disabled",true); //请求登入接口 $.ajax({ type: 'POST', url: ctx + '/login', data: obj.field, dataType: 'json', success: function(result) { if (result.code === 200) { layer.msg('登入成功', { icon: 1 ,time: 1000 }, function(){ window.location.href = '/'; }); } else { layer.msg(result.message); // 刷新验证码 getImgCode(); // 关闭loading layer.close(loading); // 开启点击事件 $('.layui-btn').attr("disabled", false); } } }); }); $("#codeImg").on('click', function() { // 添加验证码 getImgCode(); }); $(document).keydown(function (e) { if (e.keyCode === 13) { $('.layui-btn').click(); } }); // 解决session过期跳转到登录页并跳出iframe框架 $(document).ready(function () { if (window != top) { top.location.href = location.href; } }); }); /** * 获取验证码 */ function getImgCode() { let url = ctx + '/getImgCode'; let xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.responseType = "blob"; xhr.onload = function() { if (this.status === 200) { let blob = this.response; document.getElementById("codeImg").src = window.URL.createObjectURL(blob); } } xhr.send(); } </script> </body> </html>
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。