Spring Security

Spring Security:概述

前言

       在之前介绍过了Shiro之后,有好多粉丝问SpringSecurity在Spring Boot中怎么集成。这个系列我们就和大家分享下有关这方面的知识。

本节大纲

一、什么是SpringSecurity?

二、常用安全框架

一、什么是Spring Security?

SpringSecurity是基于Spring AOP和Servlet过滤器的安全框架。它提供全面的安全性解决方案,同时在Web 请求级和方法调用级处理身份确认和授权。在 Spring Framework 基础上,Spring Security 充分利用了依赖注入(DI,Dependency Injection)和面向切面编程(AOP)功能,为应用系统提供声明式的安全访问控制功能,减少了为企业系统安全控制编写大量重复代码的工作。它是一个轻量级的安全框架,它确保基于Spring的应用程序提供身份验证和授权支持。它与Spring MVC有很好地集成,并配备了流行的安全算法实现捆绑在一起。安全主要包括两个操作“认证”与“验证”(有时候也会叫做权限控制)。“认证”是为用户建立一个其声明的角色的过程,这个角色可以一个用户、一个设备或者一个系统。“验证”指的是一个用户在你的应用中能够执行某个操作。在到达授权判断之前,角色已经在身份认证过程中建立了。

   我们简单来理解一下上面这段话:

第一:什么是Spring Security ?

Spring Security是一个安全框架。

第二:Spring Security核心功能?

(1)认证(你是谁,用户/设备/系统)

(2)验证(你能干什么,也叫权限控制/授权,允许执行的操作)

(3)攻击防护(防止伪造身份)

第三:Spring Security原理技术

Filter、Servlet、Spring DI、SpringAOP

二、常用安全框架

    目前常用的安全框架主要是Spring Security和Apache Shiro,它们的区别是什么呢?

2.1 相同点

(1)认证功能

(2)授权功能

(3)加密功能

(4)会话功能

(5)缓存支持

(6)remeberMe功能

2.2 不同点(Spring Security PK Apache Shiro

优点:

(1)Spring Security基于Spring开发,项目中如果使用Spring作为基础,配合Spring Security做权限更加方便。而Shiro需要和Spring进行整合。

(2)Spring Security功能比Shiro更加丰富,例如安全防护方面。

(3)Spring Security社区资源相对比Shiro更加丰富。

(4)如果使用的是Spring Boot,Spring Cloud的话,三者可以无缝集成。

缺点:

(1)Shiro的配置和使用比较简单Spring Security上手复杂些

(2)Shiro依赖性低,不需要任何框架和容器,可以独立运行,而Spring Security依赖Spring容器

       企业里选择哪个安全框架,是因人而异,因团队而异,擅长哪个选择哪个,大部分的业务场景,两个框架都是可以满足需求的。

       本节就到这里,下节我们通过编码对于Spring Security有一个基本的认识。

Spring Boot+Spring Security:初体验

说明

(1)JDK版本:1.8

(2)Spring Boot 2.0.6

(3)Spring Security 5.0.9

需求缘起

       在上一篇文章中,我们对于Spring Security有了一个基本的了解,那么重点是在Spring Boot中如何使用Spring Security呢?

一、Spring Security初体验

       这里我们通过简单的集成方式来对Spring Security有一个基本的认知。

1.1 创建项目

 创建一个项目,取名为:springboot2-springSecurity01

1.2 添加依赖

       在pom.xml文件中添加依赖,主要是springboot parent starter和-start-web以及spring security的依赖。

spring-boot-starter-parent依赖:

   <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.6.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

Spring Security和-web依赖:

<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>

 

1.3 创建Spring Boot启动类

       使用@SpringBootApplication创建Spring Boot启动类,如下代码:

 

package com.kfit;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Springboot2SpringSecurity01Application {

    public static void main(String[] args) {
        SpringApplication.run(Springboot2SpringSecurity01Application.class, args);
    }
}

       如果使用的STS开发工具的话,上面的1.2和1.3都可以跳过。

1.4 编写Controller

       随意编写一个测试Controller,这里取名为HelloController,如下示例代码:

 

package com.kfit.demo.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/hello")
public class HelloController {

    @GetMapping
    public String getWelcomeMsg() {       
        return "Hello,Spring Security";
    }
}

 

 

 

 

 

 

 

1.5 启动测试

       启动应用程序,然后进行测试:

(1)访问如下地址:

http://localhost:8080/hello

如果接口能正常访问,那么应该显示“Hello,SpringSecurity”,但是我们是没法正常访问的,出现了下图的身份验证输入框:

这是因为在SpringBoot中,默认的Spring Security就是生效了的,此时的接口都是被保护的,我们需要通过验证才能正常的访问。 Spring Security提供了一个默认的用户,用户名是user,而密码则是启动项目的时候自动生成的。

我们查看项目启动的日志,会发现如下的一段Log:

Using generated securitypassword: 74adcd57-f0be-46c3-87cc-6d9d712cbc27

我们直接用user和启动日志中的密码进行登录,登录成功后,就跳转到了接口正常调用的页面了。

二、小技巧

2.1 关闭验证功能

      如果一开始不想使用验证功能,怎么关闭呢?

在配置文件中配置

security.basic.enabled=false

的方式,在5.x版本之后提示过时了,那么应该怎么办呢?

方式一:在启动类中排除

SecurityAutoConfiguration,

如下示例代码:

@SpringBootApplication(exclude=SecurityAutoConfiguration.class)
public class Springboot2SpringSecurity01Application {

    public static void main(String[] args) {
        SpringApplication.run(Springboot2SpringSecurity01Application.class, args);
    }
}

 

       另外在Spring Boot 2.x中下面配置项被废弃了:

security.basic.authorize-mode
security.basic.enabled
security.basic.path
security.basic.realm
security.enable-csrf
security.headers.cache
security.headers.content-security-policy
security.headers.content-security-policy-mode
security.headers.content-type
security.headers.frame
security.headers.hsts
security.headers.xss
security.ignored
security.require-ssl
security.sessions

2.2 自定义用户名和密码

   上面的用户名是默认的,密码是随时生成的,实在是不方便,那么怎么自定义用户名和密码呢,只需要在配置文件application.properties文件中添加如下配置:

spring.security.user.name = admin
spring.security.user.password = 123456

Spring Boot+Spring Security:基于内存的认证信息

说明

(1)JDK版本:1.8

(2)Spring Boot 2.0.6

(3)Spring Security 5.0.9

需求缘起

       上面我们简单体验了下Spring Security,但是现在只能有一个用户信息,我们这里希望可以配置多个账号信息,本节主要讲解下如何在内存中配置认证信息。

编码思路

       我们要在内存中初始化我们的认证信息的话,那么需要是重写WebSecurityConfigurerAdapter类中的configure方法:

configure(AuthenticationManagerBuilder auth)

然后通过auth对象的inMemoryAuthentication()方法指定认证信息:

auth.inMemoryAuthentication().withUser("admin").password("123456");

一、基于内存的认证信息

       我们基于上一篇文章中的项目springboot2-springSecurity01进行编码。

1.1 创建一个配置类

       我们定义一个WebSecurityConfig,继承WebSecurityConfigurerAdapter,如下代码:

package com.kfit.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        /*
         * 配置为从内存中进行加载认证信息.
         * 这里配置了两个用户 admin和user
         */
        auth.inMemoryAuthentication().withUser("admin").password("123456").roles();
        auth.inMemoryAuthentication().withUser("user").password("123456").roles();
    }
}

说明:

(1)@Configuration:注解这是一个配置类。

(2)@EnableWebSecurity:注解开启Spring Security的功能。

(3)WebSecurityConfigurerAdapter:继承WebSecurityConfigurerAdapter,并重写它的方法来设置一些web安全的细节。

(4)configure(AuthenticationManagerBuilder auth):在内存中创建了用户admin/user,密码为123456。

       如果是5.x之前的版本的话,那么到这里启动的话,就可以正常访问了,但是如果是5.x的版本的话,可以正常启动,但是在登录页面输入admin/123456账号进行访问的话,会报如下错误:

java.lang.IllegalArgumentException:There is no PasswordEncoder mapped for the id "null"

       这是因为Spring Security 5.0中新增了多种加密方式,也改变了密码的格式。

1.2 密码加密

       上面异常原因就是因为没有指定加密方式,那么怎么指定呐?

1.2.1 方式一:通过AuthenticationManagerBuilder指定

       在AuthenticationManagerBuilder的方法中就可以指定加密方式了,如下代码:

auth.inMemoryAuthentication()
    .passwordEncoder(new BCryptPasswordEncoder())
    .withUser("admin")
    .password(new BCryptPasswordEncoder().encode("123456"))
    .roles();

auth.inMemoryAuthentication()
    .passwordEncoder(new BCryptPasswordEncoder())
    .withUser("user")
    .password(new BCryptPasswordEncoder().encode("123456"))
    .roles();

说明:

(1)Bcrypt: bcrypt是一种跨平台的文件加密工具。bcrypt 使用的是布鲁斯·施内尔在1993年发布的 Blowfish 加密算法。由它加密的文件可在所有支持的操作系统和处理器上进行转移。它的口令必须是8至56个字符,并将在内部被转化为448位的密钥。

1.2.2 方式二:通过@Bean注入指定PasswordEncoder

       在WebSecurityConfig配置类中,通过@Bean注入PasswordEncoder具体的实现类,如下代码:

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

       加入这个之后,需要修改configure的密码加密:

protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        /*
         * 配置为从内存中进行加载认证信息.
         * 这里配置了两个用户 admin和user
         */
        auth.inMemoryAuthentication()
            .withUser("admin")
            .password(passwordEncoder().encode("123456"))
            .roles();

        auth.inMemoryAuthentication()
            .withUser("user")
            .password(passwordEncoder().encode("123456"))
            .roles();
}

对于上面的两种方式,推荐使用第二种方式,这种方式一方面更容易理解,另外代码扩展性更强。

Spring Boot+Spring Security:基于内存的角色授权

说明

(1)JDK版本:1.8

(2)Spring Boot 2.0.6

(3)Spring Security 5.0.9

需求缘起

       之前我们基于内存的方式,构建了两个账号admin和user,对于这两个账号在实际项目中会有不同的角色,比如管理员角色和普通用户角色,对于不同的角色,那么允许访问的方法会不一样

编码思路

       对于不同角色具有不同方法的权限的问题,主要需要思考几个点:

(1)如何给指定的用户指定角色

通过AuthenticationManagerBuilder的roles()方法,就可以指定角色,示例代码:

auth.inMemoryAuthentication()
            .withUser("admin")
            .password(passwordEncoder().encode("123456"))
            .roles("beijingAdmin","shanghaiAdmin");

 上面的示例中指定了用户admin,具有beijingAdmin,shanghaiAdmin的角色。

(2)如何开启方法级别安全控制

    想要开启Spring方法级安全,你需要在已经添加了@Configuration注解的类上再添加@EnableGlobalMethodSecurity注解即可。

(3)如何配置方法级别的权限控制

       使用注解@PreAuthorize("hasAnyRole('admin')")即可指定访问级别的角色。

一、基于内存的角色授权

       这里基于上一篇文章进行编码。

1.1 为用户配置角色

       通过AuthenticationManagerBuilder的roles()方法分配角色:

  @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {

        /*
         * 配置为从内存中进行加载认证信息.
         * 这里配置了两个用户 admin和user
         */
        auth.inMemoryAuthentication()
            .withUser("admin")
            .password(passwordEncoder().encode("123456"))
            .roles("admin");

        auth.inMemoryAuthentication()
            .withUser("user")
            .password(passwordEncoder().encode("123456"))
            .roles("normal");
    }

1.2 开启Spring方法级安全

       想要开启Spring方法级安全,你需要在已经添加了@Configuration注解的类上再添加@EnableGlobalMethodSecurity注解

(1)prePostEnabled :决定Spring Security的前注解是否可用 [@PreAuthorize,@PostAuthorize,..]

(2)secureEnabled : 决定是否Spring Security的保障注解 [@Secured] 是否可用。

(3)jsr250Enabled :决定 JSR-250 annotations 注解[@RolesAllowed..] 是否可用。

     我们这里在WebSecurityConfig进行添加配置:

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
}

说明:只有添加了@EnableGlobalMethodSecurity(prePostEnabled=true)之后,@PreAuthorize("hasAnyRole('admin')")才能生效。

1.3 配置方法级拥有的角色

       我们在HelloController添加两个方法,使用注解@PreAuthorize指定访问该方法需要的角色:

@RestController
@RequestMapping("/hello")
public class HelloController {

    @GetMapping
    public String getWelcomeMsg() {       
        return "Hello,Spring Security";
    }


    @GetMapping("/helloAdmin")
    @PreAuthorize("hasAnyRole('admin')")
    public String helloAdmin() {       
        return "Hello,admin";
    }

    @GetMapping("/helloUser")
    @PreAuthorize("hasAnyRole('admin','normal')")    
    public String helloUser() {       
        return "Hello,user";
    }
}

1.4 测试角色

       到这里就可以启动测试下:

(1)启动应用程序,访问如下地址:

http://127.0.0.1:8080/hello/helloUser

跳转到登录页面,输入账号user/123456,成功登录之后,会看到返回信息:Hello,user

然后在输入另外一个地址:

http://127.0.0.1:8080/hello/helloAdmin

这时候会看到403的报错:

(2)重新启动应用程序,输入admin/123456账号进行测试,都能正常访问。

Spring Boot+Spring Security:基于内存数据库的身份认证和角色授权

说明

(1)JDK版本:1.8

(2)Spring Boot 2.0.6

(3)Spring Security 5.0.9

(4)Spring Data JPA 2.0.11.RELEASE

(5)hibernate5.2.17.Final

(6)hsqldb2.4.1

需求缘起

       在前面我们使用基于内存的方式体验了下Spring Security,在实际项目中,都是需要数据库进行操作的,本节使用hsqldb内存数据库进行说明。

编码思路

       这里我们使用的是Spring Data JPA进行操作数据库,所以需要添加相关的依赖;其次就是需要定义一个保存用户基本的实体类;再者需要定义相应的服务获取用户的信息;最后重写UserDetailsService的loadUserByUsername方法从数据库中获取用户信息,传给Spring Security进行处理。

一、基于内存数据库的身份认证和角色授权

1.1 添加依赖

       在pom.xml文件中添加SpringData JPA和hsqldb的依赖:

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

        <dependency>
            <groupId>org.hsqldb</groupId>
            <artifactId>hsqldb</artifactId>
            <scope>runtime</scope>
        </dependency>

1.2 创建实体类

       创建UserInfo实体类:

package com.kfit.permission.bean;

import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;

@Entity
public class UserInfo {

    public enum Role{
        admin,normal
    }


    @Id @GeneratedValue
    private long uid;//主键.

    private String username;//用户名.
    private String password;//密码.

    @Enumerated(EnumType.STRING)
    private Role role;

    public long getUid() {
        return uid;
    }

    public void setUid(long uid) {
        this.uid = uid;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Role getRole() {
        return role;
    }

    public void setRole(Role role) {
        this.role = role;
    }


1.3 创建Repository

       创建和数据库交互的UserInfoRepository:

package com.kfit.permission.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import com.kfit.permission.bean.UserInfo;

public interface UserInfoRepository extends JpaRepository<UserInfo,Long> {

    public UserInfo findByUsername(String username);

}

1.4 创建Service

       创建UserInfoService:

package com.kfit.permission.service;

import com.kfit.permission.bean.UserInfo;

public interface UserInfoService {

    public UserInfo findByUsername(String username);

}

创建UserInfoService的实现类:

package com.kfit.permission.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.kfit.permission.bean.UserInfo;
import com.kfit.permission.repository.UserInfoRepository;
import com.kfit.permission.service.UserInfoService;

@Service
public class UserInfoServiceImpl implements UserInfoService {

    @Autowired
    private UserInfoRepository userInfoRepository;

    @Override
    public UserInfo findByUsername(String username) {
        return userInfoRepository.findByUsername(username);
    }

}

1.5 自定义UserDetailsService

       自定义一个UserDetailsService,取名为CustomUserDetailService,该类需要实现接口UserDetailsService,主要是实现loadUserByUsername方法:

package com.kfit.config;

import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import com.kfit.permission.bean.UserInfo;
import com.kfit.permission.service.UserInfoService;

@Component
public class CustomUserDetailService implements UserDetailsService{

    @Autowired
    private UserInfoService userInfoService;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("CustomUserDetailService.loadUserByUsername()");
        //通过username获取用户信息
        UserInfo userInfo = userInfoService.findByUsername(username);
        if(userInfo == null) {
            throw new UsernameNotFoundException("not found");
        }


        //定义权限列表.
        List<GrantedAuthority> authorities = new ArrayList<>();
        // 用户可以访问的资源名称(或者说用户所拥有的权限) 注意:必须"ROLE_"开头
        authorities.add(new SimpleGrantedAuthority("ROLE_"+userInfo.getRole().name()));


        User userDetails = new User(userInfo.getUsername(),passwordEncoder.encode(userInfo.getPassword()),authorities);
        return userDetails;
    }

}

说明:

(1)    通过username获取用户的信息。

(2)    定义一个User(实现了接口UserDetails)对象,返回用户的username,passowrd和权限列表。

(3)    需要注意,定义角色集的时候,需要添加前缀“ROLE_”。

(4)    这里的密码需要使用PasswordEncoder进行加密,否则会报“无效的凭证”。

1.6 初始化测试账号

       这里我们使用一个DataInit类,初始化两个账号admin/123和user/123:

package com.kfit.permission.init;

import javax.annotation.PostConstruct;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.kfit.permission.bean.UserInfo;
import com.kfit.permission.bean.UserInfo.Role;
import com.kfit.permission.repository.UserInfoRepository;

@Service
public class DataInit {

    @Autowired UserInfoRepository userInfoRepository;


    @PostConstruct
    public void dataInit() {

        UserInfo admin = new UserInfo();
        admin.setUsername("admin");
        admin.setPassword("123");
        admin.setRole(Role.admin);
        userInfoRepository.save(admin);


        UserInfo user = new UserInfo();
        user.setUsername("user");
        user.setPassword("123");
        user.setRole(Role.normal);
        userInfoRepository.save(user);
    }

}

1.7 启动测试

(1)测试账号:user/123

       启动应用访问地址:

http://127.0.0.1:8080/hello/helloUser

自动跳转到登录页面,输入账号user/123,可以看到页面:

       紧接着访问地址:

http://127.0.0.1:8080/hello/helloAdmin

       访问被拒绝:

(2)测试账号:admin/123

       现在没有退出按钮,只能重新启动应用程序,然后输入账号admin/123,上面的两个地址应该都是可以正常访问的。

Spring Boot+Spring Security:基于MySQL数据库的身份认证和角色授权

说明

(1)JDK版本:1.8

(2)Spring Boot 2.0.6

(3)Spring Security 5.0.9

(4)Spring Data JPA 2.0.11.RELEASE

(5)hibernate5.2.17.Final

(6)MySQLDriver 5.1.47

(7)MySQL 8.0.12

需求缘起

       在前面使用的是内存数据库,本节使用MySQL数据库进行操作。

编码思路

       在《基于内存数据库的身份认证和角色授权》的代码转换为MySQL数据库的存储方式的话,非常的简单,只需要添加MySQL数据库的驱动包以及配置好数据源和JPA

一、基于MySQL数据库的身份认证和角色授权

1.1 添加依赖

       在pom.xml文件中去掉hsqldb的依赖,然后添加mysql的依赖:

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

1.2 添加配置信息

       在application.properties文件中添加配置信息:

spring.datasource.url = jdbc:mysql://localhost:3306/spring-security
spring.datasource.username = root
spring.datasource.password = root
spring.datasource.driverClassName = com.mysql.jdbc.Driver

# Specify the DBMS  
spring.jpa.database = MYSQL
# Show or not log for each sql query  
spring.jpa.show-sql = true
# Hibernate ddl auto (create, create-drop, update)  
spring.jpa.hibernate.ddl-auto = create-drop

1.3 启动测试

启动应用程序,成功的话可以看到数据库的数据:

     按照之前的流程测试下,结果是一样的。到这里的话,我并没有做过多的编码,那么这么轻松从内存数据库转换到MySQL数据库,这就是框架给我们提供的便利。

二、数据库密码加密保存

       到这里基本的流程都是没有问题的,但是我们发现数据库的密码都是明文显示的,这个就要命了,那么数据库的密码怎么加密保存呢?其实也很简单,在初始化用户信息的时候,就进行加密就可以了,具体的操作如下:

2.1 修改DataInit

       修改DataInit类,注入PasswordEncoder,使用PasswordEncoder的encode方法对密码进行加密:

package com.kfit.permission.init;

import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.kfit.permission.bean.UserInfo;
import com.kfit.permission.bean.UserInfo.Role;
import com.kfit.permission.repository.UserInfoRepository;

@Service
public class DataInit {

    @Autowired 
    private UserInfoRepository userInfoRepository;

    @Autowired 
    private PasswordEncoder passwordEncoder;

    @PostConstruct
    public void dataInit() {

        UserInfo admin = new UserInfo();
        admin.setUsername("admin");
        admin.setPassword(passwordEncoder.encode("123"));
        admin.setRole(Role.admin);
        userInfoRepository.save(admin);


        UserInfo user = new UserInfo();
        user.setUsername("user");
        user.setPassword(passwordEncoder.encode("123"));
        user.setRole(Role.normal);
        userInfoRepository.save(user);
    }

}

2.2 修改CustomUserDetailService

       在添加用户的时候,已经加密了,那么在loadUserByUsername方法中返回的UserDetails就不需要再加密了,修改为如下:

package com.kfit.config;

import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.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.Component;

import com.kfit.permission.bean.UserInfo;
import com.kfit.permission.service.UserInfoService;

@Component
public class CustomUserDetailService implements UserDetailsService{
    @Autowired
    private UserInfoService userInfoService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("CustomUserDetailService.loadUserByUsername()");
        //通过username获取用户信息
        UserInfo userInfo = userInfoService.findByUsername(username);
        if(userInfo == null) {
            throw new UsernameNotFoundException("not found");
        }

        //定义权限列表.
        List<GrantedAuthority> authorities = new ArrayList<>();
        // 用户可以访问的资源名称(或者说用户所拥有的权限) 注意:必须"ROLE_"开头
        authorities.add(new SimpleGrantedAuthority("ROLE_"+userInfo.getRole().name()));

        User userDetails = new User(userInfo.getUsername(),userInfo.getPassword(),authorities);
        return userDetails;
    }

}

2.3 启动测试

       启动应用,查看数据库的用户信息:

     此时看到的密码已经是加密的了,访问下如下的地址:http://127.0.0.1:8080/hello/helloUser输入账号user/123看是否可以正常登录吧。

Spring Boot+Spring Security:自定义登录页面和构建主页

说明

(1)JDK版本:1.8

(2)Spring Boot 2.0.6

(3)Spring Security 5.0.9

(4)Spring Data JPA 2.0.11.RELEASE

(5)hibernate5.2.17.Final

(6)MySQLDriver 5.1.47

(7)MySQL 8.0.12

需求缘起

       在上一节我们已经能够把用户信息持久化到数据库了,十万里长征也完成了一大半了,黎明的曙光就要到来了。本节说明下如何自定义登录、主界面等页面。

一、准备工作

1.1 添加模板引擎

       这里使用了thymeleaf模板引擎,在pom.xml进行添加:

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

1.2 配置Spring Security的登录页面路径

       在WebSecurityConfig复写configure(HttpSecurityhttp)方法,复写登录页面的路径,如下示例代码:

   @Override
    protected void configure(HttpSecurity http) throws Exception {  
        http.authorizeRequests() // 定义哪些URL需要被保护、哪些不需要被保护
            .antMatchers("/login").permitAll()// 设置所有人都可以访问登录页面
            .anyRequest().authenticated()  // 任何请求,登录后可以访问
            .and()
            .formLogin().loginPage("/login")
            ;

    }

二、自定义登录界面

2.1 编写登录页面的html文件

       在templates下编写login.html文件,内容如下:

<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"
      xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">
    <head>
        <title>Spring Security Login </title>
    </head>
    <body>
        <div th:if="${param.error}">
            用户名密码错误,要不去<a th:href="@{/}">首页</a>看看?
        </div>
        <div th:if="${param.logout}">
            您已经登出
        </div>
        <form th:action="@{/login}" method="post">
            <div><label> 用户名 : <input type="text" name="username"/> </label></div>
            <div><label> 密码: <input type="password" name="password"/> </label></div>
            <div><input type="submit" value="登录"/></div>
        </form>
    </body>
</html>

 

2.2 编写登录页面请求映射

       创建一个HomeController类,添加login方法,使用@GetMapping进行请求映射,如下示例代码:

package com.kfit.permission.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {


    @GetMapping("/login")
    public String login() {
        return "/login";
    }

}

2.3 启动测试

       启动应用程序,访问到的登录页面已经是我们自己编写的页面了:

三、构建主页

3.1 编写主页的html文件

       编写登录页面index.html,如下示例代码:

<!DOCTYPE html>  
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity3">  
    <head>
        <title>Spring Security入门</title>
    </head>
    <body>
        <h1>欢迎使用Spring Security!</h1>
        <p>点击 <a th:href="@{/hello}">这里</a> 打个招呼吧</p>

        <p> <a th:href="@{/hello/helloAdmin}">admin page</a></p>
        <p><a th:href="@{/hello/helloUser}">user page</a></p>

    </body>
</html>

 

3.2 编写主页页面请求映射

       在HomeController中添加index映射方法:

 @GetMapping({"","/","/index"})
    public String index() {
        return "/index";
    }

3.3 测试

       启动登录可以看到如下页面:

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值