当前位置:首页 » 《资源分享》 » 正文

spring security 记住我在web和前后端分离如何使用

25 人参与  2024年12月11日 14:01  分类 : 《资源分享》  评论

点击全文阅读


一、传统web开发准备工作

如果不懂原理的话,去看上一篇文章:CSDNicon-default.png?t=N7T8https://mp.csdn.net/mp_blog/creation/editor/141716695

导入需要的依赖包,在传统web页面开发比较简单,我们设置只需要在页面请求参数加上一个remember-me 即可,值可以为:true,on,yes,1 均可

 <dependencies>        <!-- web 支持 -->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-web</artifactId>        </dependency>        <!-- SpringSecurity依赖-->        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-security</artifactId>        </dependency>        <dependency>            <groupId>org.springframework.boot</groupId>            <artifactId>spring-boot-starter-test</artifactId>        </dependency>        <!-- 简化get set 方法-->        <dependency>            <groupId>org.projectlombok</groupId>            <artifactId>lombok</artifactId>            <version>1.18.30</version>        </dependency>        <!-- mybatis 支持 -->        <dependency>            <groupId>org.mybatis.spring.boot</groupId>            <artifactId>mybatis-spring-boot-starter</artifactId>            <version>2.2.0</version>        </dependency>        <!-- mysql 驱动 -->        <dependency>            <groupId>mysql</groupId>            <artifactId>mysql-connector-java</artifactId>            <version>8.0.30</version>        </dependency>    </dependencies>    <build>        <plugins>            <plugin>                <groupId>org.springframework.boot</groupId>                <artifactId>spring-boot-maven-plugin</artifactId>            </plugin>        </plugins>    </build>

1.1  效果图

1.2 前端代码,自定义登录页面

<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head>    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">    <link rel="stylesheet" href="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/css/bootstrap.min.css">    <script src="https://cdn.staticfile.org/jquery/2.1.1/jquery.min.js"></script>    <script src="https://cdn.staticfile.org/twitter-bootstrap/3.3.7/js/bootstrap.min.js"></script>    <title>登录</title>    <style type="text/css">        .form-horizontal{margin-top: 3%;}    </style></head><body><form role="form" class="form-horizontal" th:action="@{/doLogin.do}" method="post">    <div class="form-group">        <label class="control-label col-sm-2">账号</label>        <div class="col-sm-8">            <input type="text" class="form-control" name="loginId" placeholder="请输入登录账号">        </div>    </div>    <div class="form-group">        <label class="control-label col-sm-2">密码</label>        <div class="col-sm-8">            <input type="text" class="form-control" name="pwd"  placeholder="请输入密码">        </div>    </div>   <!-- <div class="form-group">        <label class="control-label col-sm-2">图形验证码</label>        <div class="col-sm-4">            <input type="text" class="form-control" name="verifyImg"  placeholder="请输入密码">        </div>        <div class="col-sm-4">            <img src="/comm/kaptcha" id="kaptcha_id">        </div>    </div>-->    <div class="form-group">        <div class="col-sm-offset-2 col-sm-10">            <div class="checkbox">                <label>                    <input type="checkbox" value="1" name="remember-me">请记住我                </label>            </div>        </div>    </div>    <div class="form-group">        <div class="col-sm-offset-2 col-sm-10">            <button type="submit" class="btn btn-default">登录</button>        </div>    </div></form></body></html>

1.3 后端代码

该地方cookie是基于数据库的,但是可以使用基于内存的方式;

@Configurationpublic class SecurityConfiguration extends WebSecurityConfigurerAdapter {    @Autowired    private DataSource mysqlDataSource;//    @Bean//    public UserDetailsService userDetailsService() {//        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();//        UserDetails userDetails = User.withUsername("root").password("{noop}123456").authorities("admin").build();//        userDetailsManager.createUser(userDetails);//        return userDetailsManager;//    }    @Bean    public UserDetailsService userDetailsService() {        return new LoginUserServiceImpl();    }    @Override    protected void configure(HttpSecurity http) throws Exception {        http.authorizeRequests()                .mvcMatchers("/").permitAll()// 登录页面可直接访问                .anyRequest().authenticated()                .and()                .formLogin()                .loginProcessingUrl("/doLogin.do") //登录请求url                .loginPage("/")//默认登录页面                .failureForwardUrl("/")//登录失败跳转url                .defaultSuccessUrl("/hello",true)//登录成功跳转页,总是到 hello页面                .usernameParameter("loginId")//用户名                .passwordParameter("pwd")                .and()                .rememberMe()//开启记住我的功能                .rememberMeParameter("remember-me")// 记住我请求参数                //.rememberMeServices(rememberMeServices())  // 指定rememberMeServices 实现                .tokenRepository(persistentTokenRepository()) //持久化令牌                .and()                .csrf().disable();    }    @Bean    public PersistentTokenRepository persistentTokenRepository() {        JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();        repository.setDataSource(mysqlDataSource);        //repository.setCreateTableOnStartup(true);//自动创建表结构        return repository;}

1.4  设置session 过期时间为1分钟,看登录后cookie值变化

二、自定义页面记住我(前后端分离)

      前后端分离需要自己重写UsernamePasswordAuthenticationFilter 处理json类型的数据,还需要重写AbstractRememberMeServices 中的org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices#rememberMeRequested 中的方法,判断是否是需要记住我,同事需要记传一个UserDetailsService,PersistentTokenRepository 对象;传UserDetailsService的对象是因为记住我解析完cookie之后,会调用 loadUserByUsername的方法重新获取一次用户信息;

2.1 效果图

2.2 代码实现

只有后端代码配置,没有前端页面设置,数据为json格式传输

2.2.1 自定义LoginFilter

用来替代UsernamePasswordAuthenticationFilter 过滤器

public class LoginFilter extends UsernamePasswordAuthenticationFilter {    @Override    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {        if (!request.getMethod().equals("POST")) {            throw new AuthenticationServiceException("Authentication method not supported: " + request.getMethod());        }        // 判断请求类型是否为json        if (MediaType.APPLICATION_JSON_VALUE.equalsIgnoreCase(request.getContentType()) || MediaType.APPLICATION_JSON_UTF8_VALUE.equalsIgnoreCase(request.getContentType())) {            try {                Map<String,String> userInfMap = new ObjectMapper().readValue(request.getInputStream(), Map.class);                String username = userInfMap.get(getUsernameParameter());                String password = userInfMap.get(getPasswordParameter());                // 记住我参数                String remembersValue = userInfMap.get(AbstractRememberMeServices.DEFAULT_PARAMETER);                request.setAttribute(AbstractRememberMeServices.DEFAULT_PARAMETER,remembersValue);                UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);                setDetails(request, authRequest);                return this.getAuthenticationManager().authenticate(authRequest);            } catch (IOException e) {                e.printStackTrace();            }        }        // 走默认逻辑        return super.attemptAuthentication(request, response);    }}

2.2.2 自定义RememberMeServices

主要是用来重写rememberMeRequested 拿到记住我的请求参数,

public class CustomerRemembersService extends PersistentTokenBasedRememberMeServices {    public CustomerRemembersService(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) {        super(key, userDetailsService, tokenRepository);    }    @Override    protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {        String paramValue = request.getAttribute(parameter).toString();        if (paramValue != null) {            if (paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on")                    || paramValue.equalsIgnoreCase("yes") || paramValue.equals("1")) {                return true;            }        }        return false;    }}

2.2.3 将loginFilter和自定义PersistentTokenBasedRememberMeServices 加入搭配配置中

@Configurationpublic class SecurityConfiguration extends WebSecurityConfigurerAdapter {    @Autowired    private DataSource mysqlDataSource;//    @Bean//    public UserDetailsService userDetailsService() {//        InMemoryUserDetailsManager userDetailsManager = new InMemoryUserDetailsManager();//        UserDetails userDetails = User.withUsername("root").password("{noop}123456").authorities("admin").build();//        userDetailsManager.createUser(userDetails);//        return userDetailsManager;//    }    @Bean    public UserDetailsService userDetailsService() {        return new LoginUserServiceImpl();    }    @Override    protected void configure(HttpSecurity http) throws Exception {        http.authorizeRequests()                .anyRequest().authenticated()                .and()                .formLogin()                .loginProcessingUrl("/doLogin.do") //登录请求url                .and()                .rememberMe()//开启记住我的功能                .rememberMeParameter("remember-me")// 记住我请求参数                .rememberMeServices(rememberMeServices())  // 指定rememberMeServices 实现                .and()                .exceptionHandling()                .authenticationEntryPoint((request, response, authException) -> {                    // 未认证提示错误信息                    Map<String,Object> resMap = new HashMap<>();                    resMap.put("code","401");                    resMap.put("msg","请认证后再来请求接口!");                    WebUtils.writeJson(response,resMap);                })                .and()                .csrf().disable();        // 替换默认的认证器        http.addFilterAt(loginFilter(), UsernamePasswordAuthenticationFilter.class);    }    @Bean    public PersistentTokenRepository persistentTokenRepository() {        JdbcTokenRepositoryImpl repository = new JdbcTokenRepositoryImpl();        repository.setDataSource(mysqlDataSource);        return repository;    }    @Bean    public RememberMeServices rememberMeServices() {        return new CustomerRemembersService("uuid"// 定义一个生成令牌的key                ,userDetailsService()    //认证数据源                ,persistentTokenRepository()  // 数据库方式        );    }    @Bean    @Override    public AuthenticationManager authenticationManagerBean() throws Exception {        return super.authenticationManagerBean();    }    @Bean    public LoginFilter loginFilter() throws Exception {        LoginFilter loginFilter = new LoginFilter();        loginFilter.setFilterProcessesUrl("/doLogin.do");// 登录请求的url        loginFilter.setUsernameParameter("loginId");        loginFilter.setPasswordParameter("pwd");        // 认证成功时候处理        loginFilter.setAuthenticationSuccessHandler(((request, response, authentication) -> {            // 认证失败时候处理            Map<String,Object> resMap = new HashMap<>();            resMap.put("code","0000");            resMap.put("msg","登录成功");            resMap.put("data",authentication);            WebUtils.writeJson(response,resMap);        }));        // 认证失败时候处理        loginFilter.setAuthenticationFailureHandler(((request, response, exception) -> {            Map<String,Object> resMap = new HashMap<>();            resMap.put("code","401");            resMap.put("msg",exception.getMessage());            WebUtils.writeJson(response,resMap);        }));        // 设置自定义的认证数据源        loginFilter.setAuthenticationManager(authenticationManagerBean());        // 设置记住我,这个地方的记住非第一次登录时候        loginFilter.setRememberMeServices(rememberMeServices());        return loginFilter;    }}

2.3 最终使用postman 测试接口

2.4 remember 请求参数源码

  为什么请求参数是yes 或者on 都可以,请求的参数的key为remember-me

三、源码资源地址:

https://download.csdn.net/download/qq_36260963/89695919


点击全文阅读


本文链接:http://zhangshiyu.com/post/199600.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1