4.连接数据库认证+会话+退出

前边的例子我们是将用户信息存储在内存中,实际项目中用户信息存储在数据库中。

本节实现从数据库读取用户信息。根据前边对认证流程研究,只需要重新定义UserDetailService即可实现根据用户账号查询数据库。

创建user_db数据库:

CREATE DATABASE `user_db` CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'

创建t_user表

CREATE TABLE `t_user` ( `id` bigint(20) NOT NULL COMMENT '用户id', `username` varchar(64) NOT NULL, `password` varchar(64) NOT NULL, `fullname` varchar(255) NOT NULL COMMENT '用户姓名', `mobile` varchar(11) DEFAULT NULL COMMENT '手机号', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC

添加依赖:

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.1.17</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.17</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>

在application.yaml配置

spring:
  thymeleaf:
    cache: false
  datasource:
    url: jdbc:mysql://localhost:3306/user_db?useSSL=false&useUnicode=true&characterEncoding=UTF-8
    username: root
    password: 18081736467xr
    driver-class-name: com.mysql.jdbc.Driver
    druid:
      filters: stat,wall,slf4j
      stat-view-servlet:
        enabled: true
        login-password: 18081736467xr
        login-username: root
        reset-enable: false
      web-stat-filter:
        enabled: true
        exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
        url-pattern: /*
      aop-patterns: com.atguigu.boot.*
      filter:
        stat:
          slow-sql-millis: 1000
          log-slow-sql: true
          enabled: true
        wall:
          enabled: true
          config:
            drop-table-allow: false

mybatis-plus:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
#  mapper-locations: classpath:com.example.blog.dao.mapper/*.xml
  # config-location: classpath:mybatisPlus/mybatis-config.xml

  global-config: # 逻辑删除配置
    db-config:
      # 代表数据库表前缀为t_
      table-prefix: t_
      logic-delete-field: flag  #全局逻辑删除字段值
      logic-delete-value: 1 # 逻辑已删除值(默认为 1)
      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)

数据库表对应的entity:

package com.example.springsecurity.entity;

import lombok.Data;

@Data
public class User
{
    private String id;

    private String username;

    private String password;

    private String fullname;

    private String mobile;
}
UserDao:
package com.example.springsecurity.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.springsecurity.entity.User;
import org.apache.ibatis.annotations.Select;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface UserDao extends BaseMapper<User>
{
    @Select("select * from t_user where username = #{userName} limit 1")
    public User findUserByUserName(String userName);
}
UserDetailsServiceImpl:
package com.example.springsecurity.service;

import com.example.springsecurity.mapper.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import com.example.springsecurity.entity.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.stereotype.Service;

import java.util.List;

@Service
public class UserDetailsServiceImpl implements UserDetailsService
{
    @Autowired
    private UserDao userDao;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException
    {
        User user = userDao.findUserByUserName(userName);
        if(user == null)
            return null;
        return org.springframework.security.core.userdetails.User.withUsername(user.getUsername()).password(user.getPassword()).authorities("vip2").build();
//        return User.withUsername().password("$2a$10$cqVXe8.rTerOSr7I3a5NZuIkFxTIvQcBCl3sSvs9qfyUcO9dd8/oK").authorities("vip2").build();
    }
}

输入账号和密码请求认证,跟踪代码。

按照我们前面讲的PasswordEncoder的使用方法,使用BCryptPasswordEncoder需要完成如下工作:

1、在安全配置类中定义BCryptPasswordEncoder

@Bean
public PasswordEncoder passwordEncoder() 
{
    return new BCryptPasswordEncoder();
}

2、loadUserByUsername方法:

是通过前端传递过来的password进行加密,再与数据库的password进行对比,所以数据库的password也需要加密才能比对成功,所以数据库中的密码应该存储BCrypt格式

会话

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。

spring security提供会话管理,认证通过后将身份信息放入SecurityContextHolder上下文,SecurityContext与当前线程进行绑定,方便获取用户身份。

1.获取用户身份

编写LoginController,实现level的测试资源,并修改loginSuccess方法,注意getUsername方法,Spring Security获取当前登录用户信息的方法为SecurityContextHolder.getContext().getAuthentication()

@RequestMapping("login-success")
    @ResponseBody
    public String loginSuccess()
    {
        return getUserName() + "登陆成功";
    }

    //获取当前用户信息
    private String getUserName()
    {
        String userName = "";
        //获取当前认证通过的用户身份
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        //用户身份
        Object principal = authentication.getPrincipal();
        if(principal == null)
        {
            userName = "匿名";
        }
        if(principal instanceof UserDetails)
        {
            UserDetails userDetails = (UserDetails) principal;
            userName = userDetails.getUsername();
        }else
        {
            assert principal != null;
            userName = principal.toString();
        }
        return userName;
    }
http.formLogin()
                .usernameParameter("userName") //配置前端页面传递的用户名name
                .passwordParameter("pwd") //配置前端页面传递的密码name
                .loginPage("/toLogin") //定制登录页
                .loginProcessingUrl("/login") //真正的登录请求,会跳转到spring Security登录验证界面
                .successForwardUrl("/login-success")//登录成功跳转的url

2.会话控制

我们可以通过以下选项准确控制会话何时创建以及Spring Security如何与之交互:

通过以下配置方式对该选项进行配置:

@Override
protected void configure(HttpSecurity http) throws Exception
 {
    http.sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);
}

默认情况下,Spring Security会为每个登录成功的用户会新建一个Session,就是ifRequired 。

若选用never,则指示Spring Security对登录成功的用户不创建Session了,但若你的应用程序在某地方新建了 session,那么Spring Security会用它的。

若使用stateless,则说明Spring Security对登录成功的用户不会创建Session了,你的应用程序也不会允许新建 session。并且它会暗示不使用cookie,所以每个请求都需要重新进行身份验证。这种无状态架构适用于REST API 及其无状态认证机制。

会话超时

可以再sevlet容器中设置Session的超时时间,如下设置Session有效期为3600s; spring boot 配置文件

server.servlet.session.timeout=3600s

session超时之后,可以通过Spring Security 设置跳转的路径。

http.sessionManagement()
    .expiredUrl("/login‐view?error=EXPIRED_SESSION")
    .invalidSessionUrl("/login‐view?error=INVALID_SESSION");

 expired指session过期,invalidSession指传入的sessionid无效。

安全会话cookie

我们可以使用httpOnly和secure标签来保护我们的会话cookie:

  • httpOnly:如果为true,那么浏览器脚本将无法访问
  • cookie secure:如果为true,则cookie将仅通过HTTPS连接发送

spring boot 配置文件:

server.servlet.session.cookie.http‐only=true
server.servlet.session.cookie.secure=true

退出

在WebSecurityConfig的protected void configure(HttpSecurity http)中配置:

@Override
protected void configure(HttpSecurity http) throws Exception 
{
    http
        .authorizeRequests()
        .and()
        .logout() (1)
        .logoutUrl("/logout") (2)
        .logoutSuccessUrl("/login‐view?logout") (3)
        .logoutSuccessHandler(logoutSuccessHandler) (4)
        .addLogoutHandler(logoutHandler) (5)
        .invalidateHttpSession(true); (6)
}

(1)提供系统退出支持,使用 WebSecurityConfigurerAdapter 会自动被应用

(2)设置触发退出操作的URL (默认是 /logout ).

  (3)退出之后跳转的URL。默认是 /login?logout 。

(4)定制的 LogoutSuccessHandler ,用于实现用户退出成功时的处理。如果指定了这个选项那么 logoutSuccessUrl() 的设置会被忽略。

(5)添加一个 LogoutHandler ,用于实现用户退出时的清理工作.默认 SecurityContextLogoutHandler 会被添加 为最后一个 LogoutHandler 。

(6)指定是否在退出时让 HttpSession 无效。 默认设置为 true。 注意:如果让logout在GET请求下生效,必须关闭防止CSRF攻击csrf().disable()。如果开启了CSRF,必须使用 post方式请求/logout

logoutHandler:

一般来说, LogoutHandler 的实现类被用来执行必要的清理,因而他们不应该抛出异常。

下面是Spring Security提供的一些实现:

  • PersistentTokenBasedRememberMeServices 基于持久化token的RememberMe功能的相关清理
  • TokenBasedRememberMeService 基于token的RememberMe功能的相关清理
  • CookieClearingLogoutHandler 退出时Cookie的相关清理 CsrfLogoutHandler 负责在退出时移除
  • csrfToken SecurityContextLogoutHandler 退出时SecurityContext的相关清理

链式API提供了调用相应的 LogoutHandler 实现的快捷方式,比如deleteCookies()。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值