依赖

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  4. <modelVersion>4.0.0</modelVersion>
  5. <parent>
  6. <groupId>org.springframework.boot</groupId>
  7. <artifactId>spring-boot-starter-parent</artifactId>
  8. <version>2.5.3</version>
  9. <relativePath/> <!-- lookup parent from repository -->
  10. </parent>
  11. <groupId>com.qinghe</groupId>
  12. <artifactId>security-test</artifactId>
  13. <version>0.0.1-SNAPSHOT</version>
  14. <name>security-test</name>
  15. <description>Demo project for Spring Boot</description>
  16. <properties>
  17. <java.version>1.8</java.version>
  18. </properties>
  19. <dependencies>
  20. <dependency>
  21. <groupId>org.springframework.boot</groupId>
  22. <artifactId>spring-boot-starter-security</artifactId>
  23. </dependency>
  24. <dependency>
  25. <groupId>org.springframework.boot</groupId>
  26. <artifactId>spring-boot-starter-web</artifactId>
  27. </dependency>
  28. <dependency>
  29. <groupId>org.springframework.boot</groupId>
  30. <artifactId>spring-boot-devtools</artifactId>
  31. <scope>runtime</scope>
  32. <optional>true</optional>
  33. </dependency>
  34. <dependency>
  35. <groupId>org.springframework.boot</groupId>
  36. <artifactId>spring-boot-configuration-processor</artifactId>
  37. <optional>true</optional>
  38. </dependency>
  39. <dependency>
  40. <groupId>org.projectlombok</groupId>
  41. <artifactId>lombok</artifactId>
  42. <optional>true</optional>
  43. </dependency>
  44. <dependency>
  45. <groupId>org.springframework.boot</groupId>
  46. <artifactId>spring-boot-starter-test</artifactId>
  47. <scope>test</scope>
  48. </dependency>
  49. <dependency>
  50. <groupId>org.springframework.security</groupId>
  51. <artifactId>spring-security-test</artifactId>
  52. <scope>test</scope>
  53. </dependency>
  54. </dependencies>
  55. <build>
  56. <plugins>
  57. <plugin>
  58. <groupId>org.springframework.boot</groupId>
  59. <artifactId>spring-boot-maven-plugin</artifactId>
  60. <configuration>
  61. <excludes>
  62. <exclude>
  63. <groupId>org.projectlombok</groupId>
  64. <artifactId>lombok</artifactId>
  65. </exclude>
  66. </excludes>
  67. </configuration>
  68. </plugin>
  69. </plugins>
  70. </build>
  71. </project>

基本使用

代码

package com.qinghe.security.config;

import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
             //所有请求 都需要认证
            .authorizeRequests().anyRequest().authenticated()
            .and()
             //表单登陆
            .formLogin()
            .and()
            .csrf().disable();
    }
}

配置

server.port=7788
spring.security.user.name=zhangbo
spring.security.user.password=123

自定义登陆页面

代码

  • SecurityConfig ```java package com.qinghe.security.config;

import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
        // 所有请求 都需要认证
        .authorizeRequests().anyRequest().authenticated()
        .and()
        // 表单登陆
        .formLogin()
            // 登陆页面
            .loginPage("/login")
            // 设置密码和用户名字段的别名
            .passwordParameter("pwd")
            .usernameParameter("username")
            // 登陆失败页面
            .failureUrl("/login?error")
            // 登陆失败处理器,failureHandler 会覆盖 failureUrl,根据业务需求配置
            // 前后端分离,一般会使用failureHandler,来返回json数据
            .failureHandler((request, response, authenticationException) -> {
                authenticationException.printStackTrace();
                request.getRequestDispatcher(request.getRequestURI()).forward(request, response);
            })
            .permitAll()
        .and()
        .csrf().disable();
}

}


- LoginController
```java
package com.qinghe.security.controller;

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

@Controller
public class LoginController {

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

配置

server.port=7788
spring.security.user.name=zhangbo
spring.security.user.password=123

spring.mvc.view.suffix=.html
  • IndexController ```java package com.qinghe.security.controller;

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

@Controller public class IndexController {

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

}

<a name="b2BJK"></a>
## 页面
<a name="WqwTW"></a>
### login.html
```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>login</title>
</head>
<body>
    <form method="post" action="/login">
        用户名:<input name="username" type="text" />
        <br/>
        密码:<input name="pwd" type="text" />
        <br/>
        <button>登陆</button>
    </form>
</body>
</html>

index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>index</title>
</head>
<body>
  ok
</body>
</html>

密码加密

自定义登陆页面 的基础上修改

package com.qinghe.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import javax.annotation.Resource;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private PasswordEncoder passwordEncoder;

    /**
     * http相关配置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // 所有请求 都需要认证
            .authorizeRequests().anyRequest().authenticated()
            .and()
            // 表单登陆
            .formLogin()
                // 登陆页面
                .loginPage("/login")
                // 设置密码和用户名字段的别名
                .passwordParameter("pwd")
                .usernameParameter("username")
                // 登陆失败页面
                .failureUrl("/login?error")
                // 登陆失败处理器,failureHandler 会覆盖 failureUrl,根据业务需求配置
                // 前后端分离,一般会使用failureHandler,来返回json数据
                .failureHandler((request, response, authenticationException) -> {
                    authenticationException.printStackTrace();
                    request.getRequestDispatcher(request.getRequestURI()).forward(request, response);
                })
                .permitAll()
            .and()
            .csrf().disable();
    }

    /**
     * 认证管理配置
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
             // 内存配置用户
             // 注意 roles 必须要,否则会报错,因为spring security 是基于rbac的
            .inMemoryAuthentication()
                .withUser("zhangbo").password(passwordEncoder.encode("123456")).roles("admin")
                .and()
                .withUser("test").password(passwordEncoder.encode("123456")).roles("user")
            .and()
             // 设置加密器
            .passwordEncoder(passwordEncoder);
    }

    /**
     * 加密器
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

UserDetailsService

内存

package com.qinghe.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

import javax.annotation.Resource;
import java.util.Collections;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private UserDetailsService userDetailsService;

    /**
     * http相关配置
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            // 所有请求 都需要认证
            .authorizeRequests().anyRequest().authenticated()
            .and()
            // 表单登陆
            .formLogin()
                // 登陆页面
                .loginPage("/login")
                // 设置密码和用户名字段的别名
                .passwordParameter("pwd")
                .usernameParameter("username")
                // 登陆失败页面
                .failureUrl("/login?error")
                // 登陆失败处理器,failureHandler 会覆盖 failureUrl,根据业务需求配置
                // 前后端分离,一般会使用failureHandler,来返回json数据
                .failureHandler((request, response, authenticationException) -> {
                    authenticationException.printStackTrace();
                    request.getRequestDispatcher(request.getRequestURI()).forward(request, response);
                })
                .permitAll()
            .and()
            .csrf().disable();
    }

    /**
     * 用户详情服务-内存版
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        User user = new User("zhangbo",passwordEncoder.encode("111111"),true,true,true,true, Collections.singleton(new SimpleGrantedAuthority("admin")));
        manager.createUser(user);
        manager.createUser(User.withUsername("test").password(passwordEncoder.encode("222222")).roles("user").build());
        return manager;
    }

    /**
     * 加密器
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

JDBC

依赖

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
  <groupId>mysql</groupId>
  <artifactId>mysql-connector-java</artifactId>
</dependency>

配置

server.port=7788
spring.mvc.view.suffix=.html

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.url=jdbc:mysql://localhost:3316/qinghe-auth?serverTimezone=GMT%2B8&characterEncoding=UTF-8&useSSL=false&autoReconnect=true

建表语句

create table users(username varchar(50) not null primary key,password varchar(500) not null,enabled boolean not null);
create table authorities (username varchar(50) not null,authority varchar(50) not null,constraint fk_authorities_users foreign key(username) references users(username));
create unique index ix_auth_username on authorities (username,authority);

代码

package com.qinghe.security.config;

import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.JdbcUserDetailsManager;

import javax.annotation.Resource;
import javax.sql.DataSource;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Resource
    private PasswordEncoder passwordEncoder;
    @Resource
    private UserDetailsService userDetailsService;

    /**
     * http相关配置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                // 所有请求 都需要认证
                .authorizeRequests().anyRequest().authenticated()
                .and()
                // 表单登陆
                .formLogin()
                // 登陆页面
                .loginPage("/login")
                // 设置密码和用户名字段的别名
                .passwordParameter("pwd")
                .usernameParameter("username")
                // 登陆失败页面
                .failureUrl("/login?error")
                // 登陆失败处理器,failureHandler 会覆盖 failureUrl,根据业务需求配置
                // 前后端分离,一般会使用failureHandler,来返回json数据
                .failureHandler((request, response, authenticationException) -> {
                    authenticationException.printStackTrace();
                    request.getRequestDispatcher(request.getRequestURI()).forward(request, response);
                })
                .permitAll()
                .and()
                .csrf().disable();
    }

    /**
     * 认证配置
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder);
    }

    /**
     * 用户详情服务-JDBC版本
     * datasource 是自动注入的
     *
     * @return
     */
    @Bean
    public UserDetailsService userDetailsService(DataSource dataSource) {
        JdbcUserDetailsManager manager = new JdbcUserDetailsManager();
        manager.setDataSource(dataSource);
        // 以下代码,可以不要,只是为了方便创建用户
        // 正式使用,在用户管理模块对用户进行操作
        // 删除用户
        //manager.deleteUser("zhangbo");
        //manager.deleteUser("zhangyize");
        if (!manager.userExists("zhangbo")) {
            manager.createUser(User.withUsername("zhangbo").password(passwordEncoder.encode("123456")).roles("admin").build());
            // 修改用户
            manager.updateUser(User.withUsername("zhangbo").password(passwordEncoder.encode("123456")).roles("admin", "super_admin").build());
        }
        if (!manager.userExists("zhangyize")) {
            // 创建用户
            manager.createUser(User.withUsername("zhangyize").password(passwordEncoder.encode("111111")).roles("admin", "user").build());
        }

        return manager;
    }

    /**
     * 加密器
     *
     * @return
     */
    @Bean
    PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

注意:上面代码中,删除用户、创建用户、修改用户,在第一次启动的时候使用,后面可以不用。另外, UserDetailsService 服务也可以注入到别的业务中使用。

自定义

注意,这里我使用的是 MyBatis ,实际上就是查询数据,使用Redis、MongoDB、ElasticSearch、Oracle等都可以,只是查询下用户。

依赖

配置

代码

  • SecurityConfig ```java package com.qinghe.security.config;

import com.qinghe.security.service.UserService; import org.springframework.context.annotation.Bean; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;

import javax.annotation.Resource;

@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Resource
private PasswordEncoder passwordEncoder;
@Resource
private UserService userService;

/**
 * http相关配置
 *
 * @param http
 * @throws Exception
 */
@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            // 所有请求 都需要认证
            .authorizeRequests().anyRequest().authenticated()
            .and()
            // 表单登陆
            .formLogin()
            // 登陆页面
            .loginPage("/login")
            // 设置密码和用户名字段的别名
            .passwordParameter("pwd")
            .usernameParameter("username")
            // 登陆失败页面
            .failureUrl("/login?error")
            // 登陆失败处理器,failureHandler 会覆盖 failureUrl,根据业务需求配置
            // 前后端分离,一般会使用failureHandler,来返回json数据
            .failureHandler((request, response, authenticationException) -> {
                authenticationException.printStackTrace();
                request.getRequestDispatcher(request.getRequestURI()).forward(request, response);
            })
            .permitAll()
            .and()
            .csrf().disable();
}

/**
 * 认证配置
 *
 * @param auth
 * @throws Exception
 */
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth
            .userDetailsService(userService)
            .passwordEncoder(passwordEncoder);
}

/**
 * 加密器
 *
 * @return
 */
@Bean
PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
}

}


- UserService

`UserService` 只需要实现 `UserDetailsService` 就行了。
```java
package com.qinghe.security.service;

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;

@Service
public class UserService implements UserDetailsService {
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 在这里,可以使用任意数据库工具查询,我这里使用MyBatis

        return null;
    }
}