Spring Security基础--整合SpringBoot、认证流程分析、配置数据库、整合MyBatis、密码加密、会话授权功能

1.整合优点

SpringBoot 可以给我们省去很多的配置文件和工程搭建,快速导入依赖包,并且SpringBoot也是一个市面上常用的框架,可以整合市面上大部分其他框架,使用开发效率很高。

2.创建工程

我们创建一个空的工程然后引入maven依赖来搭建springboot框架。
(1)创建一个空的maven工程,引入依赖。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.mcs.security</groupId>
    <artifactId>springboot-springsecurity</artifactId>
    <version>1.0-SNAPSHOT</version>

    <parent>
        <artifactId>spring-boot-starter-parent</artifactId>
        <groupId>org.springframework.boot</groupId>
        <version>2.1.3.RELEASE</version>
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.complier.source>1.8</maven.complier.source>
        <maven.complier.target>1.8</maven.complier.target>
    </properties>

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

        <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>
            <scope>test</scope>
        </dependency>

        <!-- 数据库依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

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

        <!-- jsp 依赖 -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>jstl</artifactId>
        </dependency>

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

        <dependency>
            <groupId>org.apache.tomcat.embed</groupId>
            <artifactId>tomcat-embed-jasper</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.0</version>
        </dependency>
    </dependencies>

    <build>
    	<!-- 改为你的项目名称 -->
        <finalName>springboot-springsecurity</finalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <artifactId>maven-war-plugin</artifactId>
                    <version>3.0.0</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.2</version>
                    <!--<configuration>
                        <path>/shiro</path>
                        <port>8080</port>
                        <uriEncoding>UTF-8</uriEncoding>
                        <url>http://localhost:8080/shiro</url>
                        <server>Tomcat7</server>
                    </configuration>-->
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>

                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <encoding>utf-8</encoding>
                        <useDefaultDelimiters>true</useDefaultDelimiters>
                        <resources>
                            <resource>
                                <directory>src/main/resources</directory>
                                <filtering>true</filtering>
                                <includes>
                                    <include>**/*</include>
                                </includes>
                            </resource>
                            <resource>
                                <directory>src/main/java</directory>
                                <includes>
                                    <include>**/*.xml</include>
                                </includes>
                            </resource>
                        </resources>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>


</project>

(2)spring容器配置
springboot工程会自动扫描启动类所在包下的所有Bean,加载到spring容器。
在resource下创建application.properties
我这里默认把后面用到的配置也贴了上来

server.port=8080
server.servlet.context-path=/springboot-springsecurity

spring.application.name=springboot-springsecurity

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

# 数据库配置
spring.datasource.url=jdbc:mysql://localhost:3306/user_db
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

# 设置session超时时间
#server.servlet.session.timeout=10s

(3) SpringBoot启动类
启动类要创建在和所有子包同级目录
在这里插入图片描述

@SpringBootApplication
@Configuration
public class SecuritySpringBootApp {
    
    
    public static void main(String[] args) {
    
    
        SpringApplication.run(SecuritySpringBootApp.class, args);
    }
}

(4)servlet Context配置
相当于我们配置ssm框架时的springMVC主配置文件
这里配置不像我们上次还需要@EnableWebMvc与@ComponentScan注解,SpringBoot的自动装配机制会为我们配置。

@Configuration
public class WebConfig implements WebMvcConfigurer {
    
    

    // 默认url根路径跳转到mvc解析地址下的login
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
        registry.addViewController("/").setViewName("redirect:/login-view");
        registry.addViewController("/login-view").setViewName("login");
    }
}

视图解析器配置到application.properties文件中,比在配置文件中配置方便多了

spring.mvc.view.prefix=/WEB-INF/views/
spring.mvc.view.suffix=.jsp

(5)SpringSecurity配置
省去了@EnableWebSecurity配置

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    
    
    // 配置模拟用户信息
    @Bean
    public UserDetailsService userDetailsService() {
    
    
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
        manager.createUser(User.withUsername("lisi").password("123").authorities("p2").build());
        return manager;
    }
    // 密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
    
    
        return NoOpPasswordEncoder.getInstance();
    }

    // 配置安全拦截机制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.authorizeRequests()
                .antMatchers("/r/r1").hasAuthority("p1")
                .antMatchers("/r/r2").hasAuthority("p2")
                .antMatchers("/r/**").authenticated() // 拦截/r/**请求
                .anyRequest().permitAll() // 其他请求正常方行
                .and()
                .formLogin().successForwardUrl("/login-success");
    }
}

(6)测试
添加controller配置

@RestController
public class loginController {
    
    

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

    @RequestMapping(value = "/r/r1", produces = {
    
    "text/plain;charset=utf-8"})
    public String r1() {
    
    
        return "访问资源r1";
    }

    @RequestMapping(value = "/r/r2", produces = {
    
    "text/plain;charset=utf-8"})
    public String r2() {
    
    
        return "访问资源r2";
    }
}

3.自定义身份验证UserDetailService

springSecurity认证流程
在这里插入图片描述
1、用户提交用户名密码会先经过UsernamePasswordAuthentionFilter过滤器获取,封装为Authentication。
2、然后将Authentication提交到认证管理器AuthenticationMangere认证,认证就是一个鉴定用户身份的过程,认证成功后会返回一个Authentication对象,里面包含了身份信息,这个身份信息是一个Object,会被强转成UserDetails对象。
3、它的认证过程是什么样的呢?认证管理器将认证托管给AuthenticationProvider来认证,而AuthenticationProvider中存放了多种认证方式,比如短信认证,密码认证啥的,密码认证就是使用图上的DaoAuthenticationProvider来认证。
4、DaoAuthenticationProvider内部做了什么呢?上面提到最后返回的对象是UserDetails,从图中也可看出来,这个过程是DaoAuthenticationProvider交给UserDetailService来办,UserDetailService负责从数据库加载数据,用其loadUserByUsername方法查询数据,返回UserDetails。
5、DaoAuthenticationProvider收到了UserDetailService,就会进行密码对比,最后封装成Authentication对象返回。
6、图中最后一步呢,就是将已经登入的用户数据保存。

通过了上面的分析,大概知道了UserDetailService是来干嘛的,它就是简单的获取用户数据,那么我们就可以通过自定义实现该接口,重写loadUserByUsername方法来从指定的数据库查询用户。

前面我们是在springSecurity中模拟了用户信息,我们把前面的模拟数据注释掉,我们先实现下该接口,在loadUserByUsername中返回一个用户(相当于已经查到了),回来再改为数据库查询。

注释掉springSecurity配置文件中的模拟数据

// 配置模拟用户信息
   /* @Bean
    public UserDetailsService userDetailsService() {
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password("123").authorities("p1").build());
        manager.createUser(User.withUsername("lisi").password("123").authorities("p2").build());
        return manager;
    }*/

在service包中实现该接口

@Service 
public class SpringDataUserDetailsService implements UserDetailsService {
    
     
    @Override 
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    
     
        //登录账号 
        System.out.println("username="+username); 
        //根据账号去数据库查询... 
        //这里暂时使用静态数据 
        UserDetails userDetails = 
User.withUsername(username).password("123").authorities("p1").build(); 
        return userDetails; 
    } 
}

测试
在return userDeatils出插个断点,以Debug方式运行。

4.连接数据库

连接数据库的第一步首先得有数据库吧(说话等于放屁)
1、建表
很简单,自己建一遍就好。
在这里插入图片描述
2、引入依赖,上面的pom.xml是已经引入了该项目所需要的全部依赖。
3、在application.properties配置数据库信息
上面配置文件已经配置好了,不重复贴了
4、定义实体类

@Data
public class UserDto {
    
    
    private String id;
    private String username;
    private String password;
    private String fullname;
    private String mobile;
}

5、定义永久层Dao
因为是查询数据简单可以直接在dao里写方法,不过考虑到以后可以整合Mybatis我就使用了三层体系结构来查询,顺便把授权表所需要的实体类及方法也贴进去。
在这里插入图片描述
UserDao.java

@Repository
public interface UserDao {
    
    
    // 根据账户查询
    public UserDto getUserByUsername(String username);

    // 根据id查询权限
    public List<String> findPermissionByUserId(String userId);
}

UserDaoImpl.java

@Repository
public class UserDaoImpl implements UserDao {
    
    
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Override
    public UserDto getUserByUsername(String username) {
    
    
        String sql = "select id,username,password,fullname from t_user where username = ?";
        List<UserDto> list = jdbcTemplate.query(sql, new Object[]{
    
    username}, new BeanPropertyRowMapper<>(UserDto.class));
        if (list == null) {
    
    
            return null;
        }
        return list.get(0);
    }

    @Override
    public List<String> findPermissionByUserId(String userId) {
    
    
        // 根据用户id查询到其所拥有的权限信息
        String sql = "select * from t_permission where id in \n" +
                "(select permission_id from t_role_permission where role_id in \n" +
                "(select role_id from t_user_role where user_id = ?))";
        List<PermissionDto> list = jdbcTemplate.query(sql, new Object[]{
    
    userId}, new BeanPropertyRowMapper<>(PermissionDto.class));
        // 从权限信息中剥离出权限字段
        List<String> permissions = new ArrayList<String>(list.size());
        list.iterator().forEachRemaining(c->permissions.add(c.getCode()));
        return permissions;
    }
}

UserDaoService.java

@Service
public interface UserDaoService {
    
    
    // 根据账户查询
    public UserDto getUserByUsername(String username);

    // 根据id查询权限
    public List<String> findPermissionByUserId(String userId);
}

UserDaoSerivceImpl.java

@Service
public class UserDaoServiceImpl implements UserDaoService {
    
    
    @Autowired
    UserDao userDao;

    @Override
    public UserDto getUserByUsername(String username) {
    
    
        return userDao.getUserByUsername(username);
    }

    @Override
    public List<String> findPermissionByUserId(String userId) {
    
    
        return userDao.findPermissionByUserId(userId);
    }
}

6、修改自定义UserDetailService
将原来的模拟数据删了,直接从数据库访问数据,注意由于权限信息我们还没有在数据库建立相关表,所以权限暂时使用静态数据,即.authorities("p1")

@Service 
public class SpringDataUserDetailsService implements UserDetailsService {
    
     
 
    @Autowired
    UserDaoService userDaoService; 
 
    @Override 
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    
    
        //根据账号去数据库查询
        UserDto userByUsername = userDaoService.getUserByUsername(s); 
        if(userByUsername == null){
    
    
            return null; 
        }
        UserDetails userDetails =	User.withUsername(userByUsername .getFullname()).password(userByUsername .getPassword()).authorities("p1").build(); 
        return userDetails; 
    }
}

7、测试
用数据信息登录试试,内容较多不贴图了。

5、密码加密PasswordEncoder

DaoAuthenticationiProvider通过PasswordEncoder接口中的matches方法进行密码对比,SpringSecurity提供了很多内置的PasswordEncoder,直接在其配置文件中声明即可。
在前边springSecurity配置中使用了如下密码加密方式

    // 密码编码器
    @Bean
    public PasswordEncoder passwordEncoder() {
    
    
        return NoOpPasswordEncoder.getInstance();
    }

这是采用字符串匹配方式来进行比较并没有进行加密,只是简单比较了密码。

使用BCrypePasswordEncoder进行密码加密
1、一般密码是在注册时提交到后台再进行加密存到数据库,但我们并没有注册功能,所以就添加个测试类,将存的密码转换为BCrypePasswordEncoder格式,然后手动存到数据库。

在test中创建测试方法

@RunWith(SpringRunner.class)
public class BcryTest {
    
    
    @Test
    public void test1() {
    
    
        // 对原始密码进行加密
        String password = BCrypt.hashpw("123", BCrypt.gensalt());
        System.out.println(password);
    }
}

在这里插入图片描述
将上面的密码存到我们数据就可以了
在这里插入图片描述
2、配置加密方式
在security配置文件中配置如下

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

3、测试能否登录

6、自定义认证页面

前面我们使用的都是springSecurity自带的登录及退出界面,下面我们自定义登录界面。
1、创建登录页面
在视图解析器配置的指定地址下创建,webapp是和resource同级目录。
在这里插入图片描述

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>用户登录</title>
</head>
<body>
    <form action="login" method="post">
        用户名:<input type="text" name="username"><br>
        密码:<input type="password" name="password"><br>
        <input type="submit" value="提交">
    </form>
</body>
</html>

2、在webConfig.java中配置地址
上面应该贴过了

// 默认url根路径跳转到mvc解析地址下的login
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
    
    
        registry.addViewController("/").setViewName("redirect:/login-view");
        registry.addViewController("/login-view").setViewName("login");
    }

3、配置security
springsecurity为防止CSRF(Cross-siterequestforgery跨站请求伪造)的发生,限制了除了get以外的大多数方法,我们屏蔽CSRF即可。

// 配置安全拦截机制
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    
    
        http.csrf().disable() //屏蔽CSRF控制,即springsecurity不再限制CSRF
                .authorizeRequests()
                .antMatchers("/r/r1").hasAuthority("p1")
                .antMatchers("/r/r2").hasAuthority("p2")
                .antMatchers("/r/**").authenticated() // 拦截/r/**请求
                .anyRequest().permitAll() // 其他请求正常方行
                .and()
                .formLogin()  // 允许表单登录
                .loginPage("/login-view") // 指定自己的登录页面,已经将 ‘/’ 路径跳到试图解析器配置路径下的 ‘login’
                .loginProcessingUrl("/login") // 用户名密码提交的目的地址
                .successForwardUrl("/login-success") // 指定登录成功后跳转的url
                .permitAll() // .formLogin().permitAll()允许所有用户访问登录页面
                .and()
                .sessionManagement()  // 会话控制
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login-view?logout");
    }

7.会话功能

会话功能即我们在访问资源时显示出是谁在访问资源,我们在controller中定义了两个资源,r1,r2。
在分析认证流程中提到,最后一步会将登录的用户数据存在SecurityContextHolder上下文中,我们可通过它来访问用户信息,显示该用户访问了该资源。

修改过后的controller

@RestController
public class loginController {
    
    

    @RequestMapping(value = "/login-success", produces = {
    
    "text/plain;charset=utf-8"})
    public String loginSuccess() {
    
    
        // 获取用户信息显示出来
        return getUsername()+"登录成功";
    }

    @RequestMapping(value = "/r/r1", produces = {
    
    "text/plain;charset=utf-8"})
    public String r1() {
    
    
        return getUsername()+"访问资源r1";
    }

    @RequestMapping(value = "/r/r2", produces = {
    
    "text/plain;charset=utf-8"})
    public String r2() {
    
    
        return getUsername()+"访问资源r2";
    }

    // 获取用户姓名
    private String getUsername() {
    
    
        // Spring Security在认证完成后通过
        // SecurityContextHolder.getContext().setAuthentication()方法将Authentication保存在上下文
        // 所以我们可以通过访问上下文中的Authentication来获取用户信息
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Object principal = authentication.getPrincipal();
        String username = null;
        if(principal instanceof org.springframework.security.core.userdetails.UserDetails) {
    
    
            username = ((UserDetails) principal).getUsername();
        } else {
    
    
            username = principal.toString();
        }
        return username;
    }
}

再访问资源时即可显示相关用户信息
测试
登录张三账户,访问资源r1
在这里插入图片描述
在这里插入图片描述

8.授权

1、建表
授权功能需要访问到用户的权限信息,第一步从表中筛选出当前用户属于哪个角色,然后该角色拥有哪些权限。
角色表t_role
在这里插入图片描述
用户角色表t_user_role
在这里插入图片描述
权限表t_permission
在这里插入图片描述
角色权限表t_role_permission
在这里插入图片描述
2、在dao接口中创建查询权限方法
相关方法代码已经在第四部连接数据创建三层架构时贴上去了(太懒了)
3、将UserDetailService改为动态获取权限

@Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {
    
    
        UserDto userByUsername = userDaoService.getUserByUsername(s);
        if (userByUsername  == null) {
    
    
            return null;
        }
        // 查询权限字段信息
        String userId = userByUsername.getId();
        List<String> permissions = userDaoService.findPermissionByUserId(userId);
        // 因为返回的UserDetails创建权限是变长度的String数组,转换一下
        String[] per = new String[permissions.size()];
        permissions.toArray(per);
        // 模拟查询数据库
        UserDetails p1 = User.withUsername(userByUsername.getUsername()).password(userByUsername.getPassword()).authorities(per).build();
        return p1;
    }

9、整合Mybatis

还没写呢,下次一定

博客是在学习黑马程序员SpringSecurity时写的,有不会的可在评论区留言交流(手动呲牙)

黑马程序员SpringSecurity

猜你喜欢

转载自blog.csdn.net/qq_44660367/article/details/109551481