(五)Spring Security (spring-boot-starter-security)应用详解

前提:在看完这两篇文章的基础上再进行本篇文章的开发
(三)Spring Security (spring-boot-starter-security)应用详解
(四)spring-boot-starter-security 工作原理

连接数据库认证

前边的例子我们是将用户信息存储在内存中,实际项目中用户信息存储在数据库中,本节实现从数据库读取用户信息。根据前边对认证流程研究,只需要重新定义UserDetailService即可实现根据用户账号查询数据库。

创建数据库

创建user_db数据库

CREATE DATABASE `spring-security` CHARACTER SET 'utf8mb4' COLLATE 'utf8mb4_general_ci';

创建t_user表

CREATE TABLE `t_user` (
`id` BIGINT(20) NOT NULL COMMENT '用户id',
`username` VARCHAR(64) NOT NULL COMMENT '用户登录账号',
`password` VARCHAR(64) NOT NULL COMMENT '用户登录密码',
`fullname` VARCHAR(255) NOT NULL COMMENT '用户姓名',
`mobile` VARCHAR(11) DEFAULT NULL COMMENT '手机号',
PRIMARY KEY (`id`) USING BTREE
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT '用户表';

插入数据

INSERT INTO `t_user` (
  `id`,
  `username`,
  `password`,
  `fullname`,
  `mobile`
) 
VALUES
  (
    1,
    'zhangsan',
    '$2a$10$o/FkX0ss2mr6ligiO6AP9.z8ecTiBhJpqjew.iEGq4.s.V8r1UKRa',
    '张三',
    '16680804545'
  ),
  (
    2,
    'lisi',
    '$2a$10$InoDpP3VaYFNEPBbNrE3fOiEWFYgvIVLkuG/BzKF8jiPLjItGGFN6',
    '李四',
    '16680804545'
  ) ;

代码实现

(一)添加依赖

        
        <dependency>
            <groupId>mysqlgroupId>
            <artifactId>mysql-connector-javaartifactId>
        dependency>
        
        <dependency>
            <groupId>com.baomidougroupId>
            <artifactId>mybatis-plus-boot-starterartifactId>
            <version>3.0.5version>
        dependency>

(二)在application.properties配置

spring.datasource.url=jdbc:mysql://192.168.88.128:3306/spring-security?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver‐class‐name=com.mysql.cj.jdbc.Driver

(三)entity 创建两个实体类

package com.cyj.security.springboot.entity;

import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @program: Spring-Security-OAuth2
 * @Description:
 * @Author C_Y_J
 * @create: 2021-01-29 11:05
 **/
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("t_user")
public class User {
    private Long id;
    private String username;
    private String password;
    private String fullname;
    private String mobile;
}
package com.cyj.security.springboot.entity;

import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;

import java.util.Collection;

/**
 * @program: Spring-Security-OAuth2
 * @Description:
 * @Author C_Y_J
 * @create: 2021-01-29 11:30
 **/
@Getter
public class MyUserDetails extends User {

    /**
     * 这里我只用简单的 用户名 密码  权限集合
     * 还有一个复杂的方法
     *
     * @param username
     * @param password
     * @param authorities
     */
    public MyUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities) {
        super(username, password, authorities);
    }
}

(四) mapper

package com.cyj.security.springboot.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.cyj.security.springboot.entity.User;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

/**
 * @program: Spring-Security-OAuth2
 * @Description:
 * @Author C_Y_J
 * @create: 2021-01-29 11:18
 **/
@Mapper
@Repository
public interface UserMapper extends BaseMapper<User> {
}

(五) UserDetailService

package com.cyj.security.springboot.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.cyj.security.springboot.entity.MyUserDetails;
import com.cyj.security.springboot.entity.User;
import com.cyj.security.springboot.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
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 org.springframework.util.StringUtils;

import java.util.List;

/**
 * @program: Spring-Security-OAuth2
 * @Description:
 * @Author C_Y_J
 * @create: 2021-01-29 11:24
 **/
@Service
public class UserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        User user = userMapper.selectOne(new QueryWrapper<User>().eq("username", username));
        if (StringUtils.isEmpty(user)) {
            throw new UsernameNotFoundException("用户不存在");
        }

        //我这里弄一个简单的权限集合,真实的开发中是要查询数据库的
        List<GrantedAuthority> atuh = AuthorityUtils.commaSeparatedStringToAuthorityList("admin,p1");

        //从数据库查出的用户信息赋值给MyUserDetails对象 ,MyUserDetails对象继承了security框架中user对象
        return new MyUserDetails(
                user.getUsername(),
                user.getPassword(),
                atuh
        );
    }
}

(六)BCryptPasswordEncoder
在安全配置类中定义BCryptPasswordEncoder

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

(七) 删除以前写的UserDetailsService
(五)Spring Security (spring-boot-starter-security)应用详解_第1张图片

会话

用户认证通过后,为了避免用户的每次操作都进行认证可将用户的信息保存在会话中。spring security提供会话管理,认证通过后将身份信息放入SecurityContextHolder上下文,SecurityContext与当前线程进行绑定,方便获取用户身份。

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

    @RequestMapping(value = "/login-success", produces = {"text/plain;charset=utf-8"})
    public String loginSuccess() {
        String username = getUsername();
        return username + " 登录成功";
    }

    /**
     * 获取当前登录用户名
     *
     * @return
     */
    private String getUsername() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (!authentication.isAuthenticated()) {
            return null;
        }
        Object principal = authentication.getPrincipal();
        String username = null;
        if (principal instanceof org.springframework.security.core.userdetails.UserDetails) {
            username =
                    ((org.springframework.security.core.userdetails.UserDetails) principal).getUsername();
        } else {
            username = principal.toString();
        }
        return username;
    }

测试

登录
(五)Spring Security (spring-boot-starter-security)应用详解_第2张图片

会话控制

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

机制 描述
always 如果没有session存在就创建一个
ifRequired 如果需要就创建一个Session(默认)登录时
never SpringSecurity 将不会创建Session,但是如果应用中其他地方创建了Session,那么Spring Security将会使用它。
stateless SpringSecurity将绝对不会创建Session,也不使用Session
    /**
     * 安全拦截机制(最重要)
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .authorizeRequests()
                .antMatchers("/r/r1").hasAuthority("p1")
                .antMatchers("/r/r2").hasAuthority("p2")
                //所有/r/**的请求必须认证通过
                .antMatchers("/r/**").authenticated()
                //除了/r/**,其它的请求可以访问
                .anyRequest().permitAll();
        http
                //允许表单登录
                .formLogin()
                //自定义登录成功的页面地址
                .successForwardUrl("/login-success");

        http
                //关闭CSRF控制
                .csrf().disable();

        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有效期为60s
spring boot 配置文件:

server.servlet.session.timeout=60s

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

http
	.sessionManagement()
	.expiredUrl("/login?error=EXPIRED_SESSION")
	.invalidSessionUrl("/login?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

退出

Spring security默认实现了logout退出,访问
http://localhost:8080/security-spring-boot/logout

当退出操作出发时,将发生:
使HTTP Session 无效
清除 SecurityContextHolder
跳转到 /login
在这里插入图片描述
(五)Spring Security (spring-boot-starter-security)应用详解_第3张图片
注意:如果让logout在GET请求下生效,必须关闭防止CSRF攻击csrf().disable()。如果开启了CSRF,必须使用post方式请求/logout

        http
                //关闭CSRF控制
                .csrf().disable();

但是,类似于配置登录功能,咱们可以进一步自定义退出功能:

        http
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login?logout")
                .addLogoutHandler(logoutHandler)
                .invalidateHttpSession(true);

(1)提供系统退出支持,使用 WebSecurityConfigurerAdapter 会自动被应用
(2)设置触发退出操作的URL (默认是 /logout ).
(3)退出之后跳转的URL。默认是 /login?logout 。
(4)定制的 LogoutSuccessHandler ,用于实现用户退出成功时的处理。如果指定了这个选项那么
logoutSuccessUrl() 的设置会被忽略。
(5)添加一个 LogoutHandler ,用于实现用户退出时的清理工作.默认SecurityContextLogoutHandler 会被添加为最后一个 LogoutHandler 。
(6)指定是否在退出时让 HttpSession 无效。 默认设置为 true。

LogoutHandler

一般来说, LogoutHandler 的实现类被用来执行必要的清理,因而他们不应该抛出异常。
下面是Spring Security提供的一些实现:

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

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

原文链接

你可能感兴趣的:(安全框架)