首页 > 其他分享 >SpringBoot集成SpringSecurity并实现自定义认证

SpringBoot集成SpringSecurity并实现自定义认证

时间:2024-11-11 13:46:05浏览次数:3  
标签:web account return SpringBoot 自定义 SpringSecurity login password public

目录

一、SpringSecurity简介

二、集成SpringSecurity

1、引入依赖

2、编写核心配置类

3、数据库建表

4、自定义session失效策略

5、自定义认证

6、重写loadUserByUsername方法

7、登录页面和接口

三、总结


一、SpringSecurity简介

SpringSecurity是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架。它提供了一组可以在Spring应用上下文中配置的Bean,充分利用了Spring IoC,DI(控制反转Inversion of Control ,DI:Dependency Injection 依赖注入)和AOP(面向切面编程)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。

Spring Security登录认证主要涉及两个重要的接口 UserDetailService和UserDetails接口。
UserDetailService接口主要定义了一个方法 loadUserByUsername(String username)用于完成用户信息的查 询,其中username就是登录时的登录名称,登录认证时,需要自定义一个实现类实现UserDetailService接 口,完成数据库查询,该接口返回UserDetail。
UserDetail主要用于封装认证成功时的用户信息,即自己的User对象,但是最好是实现UserDetail接口,自定义用户对象。

二、集成SpringSecurity

环境版本:

  • java version “1.8”
  • SpringBoot 2.5.7.RELEASE
  • SpringSecurity 5.5.3.RELEASE

1、引入依赖

新建一个springboot项目,然后pom文件夹引入springSecurity的依赖包(因为我项目里面用的thymeleaf引擎,所以多引入了一个依赖):

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <!-- thymeleaf和springSecurity的扩展依赖-->
        <dependency>
            <groupId>org.thymeleaf.extras</groupId>
            <artifactId>thymeleaf-extras-springsecurity5</artifactId>
        </dependency>

2、编写核心配置类

新建WebSecurityConfig配置类并继承WebSecurityConfigurerAdapter类。

代码如下:

/**
 * <p>
 * springSecurity安全访问配置。
 * </p>
 *
 * @author 刘易彦
 * @custom.date 2024/6/21 15:23
 */
@Configuration
@EnableWebSecurity
@EnableJdbcHttpSession
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private IUserService userDetailsService;

    /**
     * 在集群环境下控制会话并发的会话注册表
     */
    @Resource
    private FindByIndexNameSessionRepository<? extends Session> sessionRepository;

    /**
     * 自定义session失效策略
     */
    @Resource
    private SpringSecurityCustomInvalidSessionStrategy springSecurityCustomInvalidSessionStrategy;

    /**
     * 数据源
     */
    @Resource
    private DataSource dataSource;

    @Resource
    private CustomAuthenticationProvider customAuthenticationProvider;

    /**
     * 忽略URL
     */
    protected static final String[] URLS = {
            // API接口
            "/web/login",
            "/logout",
            "/web/login-success",
            "/web/logout",
            "/doc.html",
            "/favicon.ico",
            "/v2/api-docs",
            "/swagger-resources/**",
            "/webjars/**"
    };

    /**
     * 忽略静态资源
     */
    protected static final String[] RESOURCES = {
            "/images/**",
            "/layui/**",
            "/lib/**",
            "/modules/**",
            "/style/**",
            "/views/**",
            "/config.js",
            "/css/**",
            "/user/**",
            "/js/**"
    };

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(customAuthenticationProvider);
    }

 

    /**
     * <p>
     * 持久化token
     * </p>
     * spring Security中,默认是使用PersistentTokenRepository的子类InMemoryTokenRepositoryImpl,将token放在内存中,<br>
     * 如果使用JdbcTokenRepositoryImpl,会创建表persistent_logins,将token持久化到数据库。
     *
     * @return {@link PersistentTokenRepository}
     * @author lyy
     * @custom.date 2024/6/21 15:23
     */
    @Bean
    public PersistentTokenRepository persistentTokenRepository() {
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        // 启动创建表,创建成功后注释掉
        //jdbcTokenRepository.setCreateTableOnStartup(true);
        // 设置数据源
        jdbcTokenRepository.setDataSource(this.dataSource);
        return jdbcTokenRepository;
    }

    /**
     * <p>
     * 维护session注册信息
     * </p>
     *
     * @return {@link SessionRegistry}
     * @author lyy
     * @custom.date 2024/6/21 15:23
     */
    @Bean
    public SessionRegistry sessionRegistry() {
        return new SpringSessionBackedSessionRegistry<>(this.sessionRepository);
    }

    /**
     * <p>
     * HttpSecurity主要针对权限控制配置。
     * </p>
     *
     * @param http {@link HttpSecurity}
     * @author lyy
     * @custom.date 2024/6/21 15:23
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                // 如果有允许匿名的url,填在下面
                .antMatchers(URLS).permitAll()
                .anyRequest().authenticated()
                .and()
                // 登录配置
                .formLogin()
                .usernameParameter("account")
                .passwordParameter("password")
                // 设置登陆页
                .loginPage("/web/login")
                // 设置登陆接口路径
                .loginProcessingUrl("/web/doLogin")
                // 设置登陆成功页
                .defaultSuccessUrl("/web/login-success", true)
                .permitAll()
                // 认证失败处理器
                //.failureHandler(this.authenticationFailureHandler)
                .and()
                .sessionManagement()
                // 自定义session失效策略
                .invalidSessionStrategy(this.springSecurityCustomInvalidSessionStrategy)
                .invalidSessionUrl("/web/login?timeout=true")
                .maximumSessions(1)
                // 当达到最大值时,是否保留已经登录的用户
                .maxSessionsPreventsLogin(false)
                // 当达到最大值时,旧用户被踢出后的操作
                .expiredUrl("/web/login?expire=true")
                .sessionRegistry(this.sessionRegistry())
                .and()
                .and()
                // 退出登录配置
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/web/logout-success")
                .permitAll()
                .invalidateHttpSession(true)
                .and()
                //.csrf()
                //.disable()
                // 允许嵌入iframe
                .headers()
                // 禁用缓存
                .cacheControl()
                .disable()
                .frameOptions()
                .disable();

        // 关闭CSRF跨域
        http.csrf().disable();
    }

    /**
     * <p>
     * WebSecurity主要针对全局请求忽略规则配置(比如说静态文件,比如说注册页面)、全局HttpFirewall配置、是否debug配置、全局SecurityFilterChain配置、privilegeEvaluator、expressionHandler、securityInterceptor等。
     * </p>
     *
     * @param web {@link WebSecurity}
     * @author lyy
     * @custom.date 2024/6/21 15:23
     */
    @Override
    public void configure(WebSecurity web){
        // web.ignoring直接绕开spring security的所有filter,直接跳过验证
        // 设置拦截忽略文件夹,可以对静态资源放行
        web.ignoring().antMatchers(RESOURCES);
    }
}

其中configure(AuthenticationManagerBuilder auth)设定了自定义认证方式(具体实现后续文章中有详解)。

3、数据库建表

用户表:

建表后记得添加测试用户。

session策略表(用于自定义用户登录session失效策略):

spring_session:

spring_session_attributes:

4、自定义session失效策略

1.新建SpringSecurityCustomInvalidSessionStrategy类并实现InvalidSessionStrategy接口。

代码如下:

/**
 * <p>
 * SpringSecurity框架下自定义session失效策略
 * </p >
 *
 * @author 刘易彦
 * @custom.date 2024/1/4 15:02
 */
@Slf4j
@Component
public class SpringSecurityCustomInvalidSessionStrategy implements InvalidSessionStrategy {

    @Override
    public void onInvalidSessionDetected(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
        String requestUri = request.getRequestURI();
        String sessionId = request.getSession().getId();
        log.info("请求路径:{},session失效,获取一个新的session:{}", requestUri, sessionId);
        // 如果是后台管理路径,则重定向到登录页面,并且提示登录已经超时
        if (StringUtils.startsWith(requestUri, "/web")) {
            // 执行请求重定向
            response.sendRedirect("/web/login?timeout=true");
        }
        // 否则重定向到原页面,这样就能获取到一个新的session,继续保持正常访问
        else {
            // 执行请求重定向
            response.sendRedirect(requestUri);
        }
    }

}

2.在核心配置类中的 configure(HttpSecurity http)添加

.invalidSessionStrategy(this.springSecurityCustomInvalidSessionStrategy)。

5、自定义认证

1.新建CustomAuthenticationProvider类并继承AbstractUserDetailsAuthenticationProvider类。

代码如下:

/**
 * <p>
 * 自定义springSecurity认证。
 * </p>
 *
 * @author 刘易彦
 * @custom.date 2024/6/21 17:23
 */
@Component
public class CustomAuthenticationProvider extends AbstractUserDetailsAuthenticationProvider {

    @Autowired
    private IUserService userService;

    @SneakyThrows
    @Override
    protected void additionalAuthenticationChecks(UserDetails userDetails, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        String account = authentication.getPrincipal().toString(); // 获取账号
        String password = authentication.getCredentials().toString(); // 获取密码
       // 拿到后端查询到的账号密码信息进行比较
        if (!StringUtils.equals(account, userDetails.getUsername())) {
            throw new BadCredentialsException("账号或密码错误!");
        }
        if (!StringUtils.equals(password, userDetails.getPassword())) {
            throw new BadCredentialsException("账号或密码错误!");
        }
    }


    @Override
    protected UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken authentication) throws AuthenticationException {
        //查询用户角色权限
        UserDetails userDetails = userService.loadUserByUsername(username);
        if (userDetails == null) {
            throw new InternalAuthenticationServiceException(
                    "UserDetailsService returned null, which is an interface contract violation");
        }
        return userDetails;
    }
}

我这里的项目因为是在内网中部署的,所以没有使用加密传输,也没有弄复杂的验证码功能,如有需要可以自行添加。

2.核心配置类添加认证方式

代码如下:

    @Resource
    private CustomAuthenticationProvider customAuthenticationProvider;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) {
        auth.authenticationProvider(customAuthenticationProvider);
    }

6、重写loadUserByUsername方法

因为SpringSecurity认证流程:loadUserByUsername()方法内部实现。并且只有这个方法能获取到前端登录的账号,因为我们需要通过用户账号去查询数据库存储的用户信息,所以我们需要重写此方法(我这里仅展示了用户信息的查询,如有角色、权限等的信息,都可以在这里进行查询并封装到SpringSecurityActiveUser类里面)。

代码如下:

/**
 * @ClassName: CustomUserDetailsService
 * @Description:
 * @Params:
 * @Author: lyy on 2022/3/21 20:37
 */

@Service("userDetailsService")
public class userDetailsServiceImpl extends ServiceImpl<IUserMapper, UserEntity>  implements IUserService{

    @Resource
    private IUserMapper userMapper;

    /**
     * <p>
     * 根据用户名获取用户
     * </p >
     *
     * @param username 用户名
     * @return {@link userDetailsServiceImpl}
     * @author lyy
     * @custom.date 2024/6/21 14:05
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
       Collection<GrantedAuthority> authorities = new ArrayList<>();
        // 用户信息
        LambdaQueryWrapper<UserEntity> userQw = new LambdaQueryWrapper<>();
        userQw.eq(UserEntity::getUserAccount, username);
        UserEntity userInfo = userMapper.selectOne(userQw);
        return new SpringSecurityActiveUser(userInfo.getUserId(), userInfo.getUserAccount(), userInfo.getUserName(),
                AuthorityUtils.commaSeparatedStringToAuthorityList("ROLE_USER"));
    }
}
/**
 * <p>
 * 在线用户
 * </p>
 *
 * @author 刘易彦
 * @since 2024-07-10
 */
@Getter
@Setter
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class SpringSecurityActiveUser extends User implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 用户ID
     */
    private Long userId;

    /**
     * 用户账号
     */
    private String userAccount;

    /**
     * 用户名
     */
    private String userName;


    public SpringSecurityActiveUser(Long userId, String userAccount, String userName,
                                    Collection<? extends GrantedAuthority> authorities) {
        super(userAccount, "", authorities);
        this.userId = userId;
        this.userAccount = userAccount;
        this.userName = userName;
        this.funAuthority = funAuthority;
    }

}

7、登录页面和接口

css代码如下:

    <style>
        .layadmin-user-login-body {
            position: relative;
            background: #fff;
            border-radius: 10px;
            border: 1px solid rgba(34, 36, 38, .15) !important;
            box-shadow: 8px 8px 8px #1a36279c;
        }

        .layadmin-user-login-header h2 {
            margin-bottom: 10px;
            font-weight: 600;
            font-size: 40px;
            color: #373737;
            text-shadow: 8px 8px 8px #1a36273b;
        }

        h2 > img {
            margin-right: 18px;
        }

        .layadmin-user-login-main {
            width: 500px;
        }

        .layadmin-user-login-box {
            padding: 50px 40px 30px;
        }

        .layadmin-user-login-icon {
            line-height: 52px;
        }

        .layui-input, .layui-textarea, .layui-btn {
            height: 52px;
            line-height: 52px;
            font-size: 16px;
        }
    </style>

html代码如下:

<img style="width: 100%;height: 100%;position: absolute;" class="disb imgset" alt="" th:src="@{/images/background.png}"/>
<div class="layadmin-user-login layadmin-user-display-show" id="LAY-user-login" style="display: none;">
    <div class="layadmin-user-login-box layadmin-user-login-header">
       <!-- <h2><img th:src="@{/images/logo2.png}"/>芯钛oa人事管理平台</h2>-->
    </div>
    <div class="layadmin-user-login-main">
        <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="account" id="LAY-user-login-username"
                       placeholder="账号" class="layui-input" autocomplete="on">
            </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" id="LAY-user-login-password"
                       placeholder="密码" class="layui-input">
            </div>
            <div class="layui-form-item login-check" th:if="${param.error}">
                <label style="color: red;">
                    账号或密码错误,请重新输入!
                </label>
            </div>
            <div class="layui-form-item login-check" th:if="${param.timeout}">
                <label style="color: red;">
                    登录已超时,请重新登录!
                </label>
            </div>
            <div id="accountCheck" class="layui-form-item login-check" style="display: none;">
                <label style="color: red;">
                    账号不能为空!
                </label>
            </div>
            <div id="passwordCheck" class="layui-form-item login-check" style="display: none;">
                <label style="color: red;">
                    密码不能为空!
                </label>
            </div>
            <!--<div class="layui-form-item" style="margin-bottom: 20px;">
                <input type="checkbox" name="remember" lay-skin="primary" title="记住密码">
            </div>-->
            <div class="layui-form-item">
                <button class="layui-btn layui-btn-fluid" lay-submit lay-filter="LAY-user-login-submit">登 入</button>
            </div>

        </div>
    </div>
</div>

 js代码如下:

let ctxPath = /*[[@{/}]]*/;
    layui.config({
        base: ctxPath // 静态资源所在路径
    }).extend({
        index: 'lib/index' //主入口模块
    }).use(['index','common','form'], function () {
        let $ = layui.$, form = layui.form, common = layui.common;
        form.render();
        //提交
        form.on('submit(LAY-user-login-submit)', function () {
            login();
        });
        $('.layui-form').keydown(function (event) {
            if (event.keyCode === 13) {
                // 回车键被按下
                event.preventDefault(); // 阻止回车键默认行为(例如提交表单)
                login();
            }
        });

        /**
         * 登录
         */
        function login() {
            // 先隐藏所有提示文字
            $('.login-check').hide();
            let $account = $('#LAY-user-login-username');
            let account = $account.val();
            if (typeof account === 'undefined' || account == null || account === '') {
                $('#accountCheck').show();
                $account.focus();
                $account.css('border-color', 'red');
                return false;
            } else {
                $('#accountCheck').hide();
                $account.css('border-color', '');
            }
            let $password = $('#LAY-user-login-password');
            let password = $password.val();
            if (typeof password === 'undefined' || password == null || password === '') {
                $('#passwordCheck').show();
                $password.focus();
                $password.css('border-color', 'red');
                return false;
            } else {
                $('#passwordCheck').hide();
                $password.css('border-color', '');
            }
            // 加密
          /*  account = common.rsaEncrypt(account);
            password = common.rsaEncrypt(password);*/
            // 提交表单
            let tempForm = document.createElement('form');
            tempForm.action = ctxPath + 'web/doLogin';
            tempForm.method = 'post';
            tempForm.style.display = 'none';
            let accountInput = document.createElement('input');
            accountInput.name = 'account';
            accountInput.value = account;
            tempForm.appendChild(accountInput);
            let passwordInput = document.createElement('input');
            passwordInput.name = 'password';
            passwordInput.value = password;
            tempForm.appendChild(passwordInput);
            document.body.appendChild(tempForm);
            tempForm.submit();
        }

        // 检查当前页面是否处于顶级窗口中
        if (window.self !== window.top) {
            // 重定向整个窗口到登录页面
            window.top.location.href = ctxPath + 'web/login';
        }
    });

 接口代码如下:

    @ApiOperation(value = "访问首页")
    @GetMapping("/index")
    public ModelAndView index() {
        SpringSecurityActiveUser activeUser = SpringSecurityUtils.getCurrentActiveUser();
        ModelAndView mv = new ModelAndView("index");
        return mv;
    }

    @ApiOperation(value = "登录页面")
    @GetMapping("/login")
    public ModelAndView login() {
        ModelAndView mv = new ModelAndView("login");
        return mv;
    }

    /**
     * <p>
     * 登录成功重定向到首页
     * </p>
     *
     * @return 重定向首页URL
     * @author lyy
     * @custom.date 2024/6/18 21:09
     */
    @GetMapping("/login-success")
    public String loginSuccess() {
        return "redirect:index";
    }

    /**
     * <p>
     * 退出登录成功重定向到首页
     * </p>
     *
     * @return 重定向首页URL
     * @author lyy
     * @custom.date 2024/6/18 21:09
     */
    @GetMapping("/logout-success")
    public String logoutSuccess() {
        return "redirect:index";
    }

三、总结

通过本文的介绍,我们集成了SpringSecurity来实现了用户登录认证以及自定义认证方式。

标签:web,account,return,SpringBoot,自定义,SpringSecurity,login,password,public
From: https://blog.csdn.net/milk_yan/article/details/143672569

相关文章

  • 【9691】基于springboot+vue的地方美食分享网站
    作者主页:Java码库主营内容:SpringBoot、Vue、SSM、HLMT、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、小程序、安卓app等设计与开发。收藏点赞不迷路 关注作者有好处文末获取免费源码项目描述困扰管理层的许多问题当中,地方美食分享管理一定是美食界不敢忽视的一块。......
  • springboot 校园设施报修管理系统-毕业设计源码33917
    摘 要随着互联网大趋势的到来,社会的方方面面,各行各业都在考虑利用互联网作为媒介将自己的信息更及时有效地推广出去,而其中最好的方式就是建立网络管理系统,并对其进行信息管理。由于现在网络的发达,校园设施报修管理通过网络进行信息管理掀起了热潮,所以针校园设施报修管理的......
  • 基于Springboot+Vue的毕业生就业推荐系统 (含源码数据库)
    1.开发环境开发系统:Windows10/11架构模式:MVC/前后端分离JDK版本:JavaJDK1.8开发工具:IDEA数据库版本:mysql5.7或8.0数据库可视化工具:navicat服务器:SpringBoot自带apachetomcat主要技术:Java,Springboot,mybatis,mysql,vue2.视频演示地址3.功能这个系......
  • SpringBoot小区防疫健康信息管理及出入登记平台mfh93 带论文文档1万字以上
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表系统内容:普通管理员,住户,小区公告,健康信息,返乡申请,出入申请,特殊事件开题报告内容一、研究背景随着全球公共卫生事件的频发,小区作为城市居民的基本生活单......
  • SpringBoot响应式企业官网开发2mutj 程序+源码+数据库+调试部署+开发环境
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表系统内容:用户,资讯信息,类型,产品信息,产品类型,招聘信息,招聘类型,投递信息开题报告内容一、研究背景与意义随着互联网技术的飞速发展,企业官网已成为展示企业......
  • SpringBoot线上学习系统的设计与实现kt003 本系统(程序+源码+数据库+调试部署+开发环境
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表系统内容:用户,讲师,学级分类,科目分类,语音课程,视频课程,语音课程购买,视频课程购买,语音课程发送,视频课程发送开题报告内容一、项目背景与意义随着互联网技......
  • Springboot计算机毕业设计家庭整理服务管理系统39774
    Springboot计算机毕业设计家庭整理服务管理系统39774本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表项目功能:用户,整理师,服务产品,订单信息,派单信息,服务订单,用户评价,预定整理师开题报告内容一、项目......
  • Springboot计算机毕业设计家乡特色推荐系统p89xk
    Springboot计算机毕业设计家乡特色推荐系统p89xk本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表项目功能:用户,文章分类,文章分享,热门文章开题报告内容一、项目背景随着互联网的普及和信息技术的飞速发展......
  • Springboot计算机毕业设计家乡印象网站984y9
    Springboot计算机毕业设计家乡印象网站984y9本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表项目功能:用户,笔记信息,标签分类,举报信息,主题活动,活动投稿,助农产品,产品分类,地区分类开题报告内容一、项目......
  • 「Java开发指南」如何自定义Spring代码生成?(二)
    搭建用户经常发现自己对生成的代码进行相同的修改,这些修改与个人风格/偏好、项目特定需求或公司标准有关,本教程演示自定义代码生成模板,您将学习如何:创建自定义项目修改现有模板来包含自定义注释使用JET和Skyway标记库中的标记配置项目来使用自定义在上文中,我们为大家介绍了......