VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > Java教程 >
  • Spring-Security

概述

  1. 认证(你是谁,户/设备/系统
  2. 验证(你能干什么,也叫权限控制/授权,允许执行的操作)
  3. 基于Filter , Servlet, AOP实现身份认证和权限验证

实例驱动学习

入门案例

1、新建工程,导入依赖

<!--spring-security相关依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

2、配置访问地址,启动测试,获得秘钥

登陆名:user

密码:查看日志

获得秘钥

3、小结

使用AOP做了拦截,拦截后再访问的servlet

security相关配置

自定义用户名和密码

在Application.yml 中配置文件上的security的user和password

spring:
  security:
    user:
      name: admin
      password: admin

关闭验证

在启动类的注解中,排除安全验证,使用内存中的用户信息

关闭验证

使用内存中的账户

使用类WebSecurityConfigurerAdapter控制安全管理内容

1. 自定义类继承WebSecurityConfigurerAdapter,声明是个配置类、开启WebSecurity
2. 重写configure方法
3. 在SpringSecurity_v5.0版本中,密码必须使用一个加密方式
4. 构建一个方法创建一个BCrypt加密类,用于加密操作(BCrypt跨平台)
@Configuration
@EnableWebSecurity
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
				// 得到一个加密类,
        PasswordEncoder passwordEncoder = passwordEncoder();
        auth.inMemoryAuthentication().withUser("zhangsan").password(passwordEncoder.encode("zhangsan")).roles();
        auth.inMemoryAuthentication().withUser("lisi").password(passwordEncoder.encode("lisi")).roles();
        auth.inMemoryAuthentication().withUser("admin").password(passwordEncoder.encode("admin")).roles();
    }
		// 构建一个方法创建一个加密类,放入容器中
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

使用数据库的账户

1、导入依赖

<!--mysql驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.23</version>
</dependency>
<!--spring-jpa依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>2.4.5</version>
</dependency>

2、封装对象,创建一个实体类,初始化数据 使用JPA连接数据库

3.1、创建一个实体类

package com.study.entity;

import lombok.Data;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
@Data
public class UserInfo {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String username;
    private String password;
    private String role;
}

3.2、创建一个dao

package com.study.dao;

import com.study.entity.UserInfo;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserInfoDao extends JpaRepository<UserInfo, Long> {
    UserInfo findByUsername(String name);
}

3.3、创建service层的类 接口和实现类

package com.study.service;
import com.study.entity.UserInfo;

public interface UserInfoService {
    UserInfo findUserInfo(String username);
}
package com.study.service.impl;

import com.study.dao.UserInfoDao;
import com.study.entity.UserInfo;
import com.study.service.UserInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class UserInfoServiceImpl implements UserInfoService {

    @Autowired
    UserInfoDao userInfoDao;

    public UserInfo findUserInfo(String username) {
        UserInfo userInfo = userInfoDao.findByUsername(username);
        return userInfo;
    }
}

3.4、配置数据库连接信息

spring.datasource.url=jdbc:mysql://8.129.121.241:3306/springsecurity?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=xxyy
spring.datasource.password=xxYY11..
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#spring-data-jpa
spring.jpa.generate-ddl=true
spring.jpa.show-sql=true
spring.jpa.database=mysql

3.5、初始化数据库数据

package com.study.init;

import com.study.dao.UserInfoDao;
import com.study.entity.UserInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

@Component
public class JdbcInit {
    @Autowired
    private UserInfoDao userInfoDao;

    @PostConstruct
    public void init() {
        PasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
        UserInfo userInfo = new UserInfo();
        userInfo.setUsername("lisi");
        userInfo.setPassword(passwordEncoder.encode("lisi"));
        userInfo.setRole("normal");
        userInfoDao.save(userInfo);
    }
}

3.6、查询数据库,构造一个User对象,用于框架中使用

package com.study.provider;

import com.study.dao.UserInfoDao;
import com.study.entity.UserInfo;
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.Service;

import java.util.ArrayList;
import java.util.List;

@Service
public class MyUserDetailService implements UserDetailsService {
    @Autowired
    private UserInfoDao userInfoDao;
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user=null;
        UserInfo userInfo=null;
        if(username!=null){
            userInfo = userInfoDao.findByUsername(username);
            if (userInfo!=null){
                List<GrantedAuthority> list=new ArrayList<GrantedAuthority>();
                GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_"+userInfo.getRole());
                list.add(authority);
                user=new User(userInfo.getUsername(),userInfo.getPassword(),list);
            }
        }
        return user;
    }
}

4、编写配置类,将通过数据库得到的User对象,进行角色配置

package com.study.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
}

5、添加Controller,最后测试验证,注意将添加数据库信息的注解注释掉

package com.study.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    @GetMapping(value = "/hello")
    public String hello() {
        return "hello spring-security";
    }

    @RequestMapping(value = "/hellouser")
    @PreAuthorize(value = "hasAnyRole('ROLE_admin','ROLE_normal')")
    public String helloUser() {
        return "hello spring-security have normail Admin role";
    }

    @RequestMapping(value = "/helloadmin")
    @PreAuthorize(value = "hasAnyRole('ROLE_admin')")
    public String helloAdmin() {
        return "hello spring-security have Admin role";
    }
}

踩坑

所有的权限都要加上 “ROLE_” 作为前缀

基于角色权限

认证和授权

  1. authentication:认证,认证访问者是谁。一 个用户或者一个其他系统是不是当前要访问的系统中的有效用户。

  2. authorization:授权,访问者能做什么?
    比如说张三用户要访问一个公司OA系统。首先系统要判断张三 是不是公司中的有效用户

  3. 例如:认证:张三是不是有效的用户,是不是公司的职员
    授权:判断张三能否做某些操作,如果张三是个领导可以批准下级的请假,其他的操作

RBAC是什么?

image-20210523153320553
  1. 权限:能对资源的操作,比如增加, 修改,删除,查看等等

  2. 角色:自定义的,表示权限的集合。一个角色可以有多个权限

  3. 用户:一个用户赋予某种权限,就等同于拥有这些权限

RBAC设计中的表

  1. 用户表:用户认证(登录用到的表)
    用户名,密码,是否启用,是否锁定等信息

  2. 角色表:定义角色信息
    角色名称,角色的描述

  3. 用户和角色的关系表:用户和角色是多对多的关系
    一个用户可以有多个角色,一个角色可以有多个用户

  4. 权限表,角色和权限的关系表
    角色可以有哪些权限

spring security中认证的接口和类

UserDetails 接口

表示用户信息

// 接口,表示用户信息
public interface UserDetails extends Serializable {
	// 权限集合
    Collection<? extends GrantedAuthority> getAuthorities();

    String getPassword();

    String getUsername();
	// 账号是否过期
    boolean isAccountNonExpired();
	// 账号是否锁定
    boolean isAccountNonLocked();
	// 证书是否过期
    boolean isCredentialsNonExpired();
	// 账号是否启用
    boolean isEnabled();
}

UserDetails实现类

package org.springframework.security.core.userdetails.User;

可以自定义类实现UserDetails接口,作为你的系统中的用户类。这个类可以交给spring security使用

UserDetailsService接口

主要作用:
获取用户信息,得到是UserDetails对象。一般项目中都需要自定义类实现这个接口,从数据库中获取数据

只有一个方法需要实现:
UserDetails loadUserByUsername(String var1) : 根据用户名称,获取用户信息(用户名称,密码,角色结合,是否可用,是否锁定等信息)

UserDetailsService接口的实现类类:

1、InMemoryUserDetailsManager :在管理内存中用户信息
优点:使用方便
缺点:数据不是持久的,系统重启后数据恢复原样

2、JdbcUserDetailsManager:用户信息存放在数据库中,底层使用jdbcTemplate操作数据库。
可以 使用JdbcUserDetailsManager中的方法完成用户的管理
createUser:创建用户
updateUser:更新用户
deleteUser:删除用户
UserExists:判断用户是否存在

数据库文件:

package org.springframework.security.core.userdetails.jdbc;
  • users.ddl 文件
create table users(username varchar_ignorecase(50) not null primary key,password varchar_ignorecase(500) not null,enabled boolean not null);
create table authorities (username varchar_ignorecase(50) not null,authority varchar_ignorecase(50) not null,constraint fk_authorities_users foreign key(username) references users(username));
create unique index ix_auth_username on authorities (username,authority);

注:建表时若使用的是MySQL,将varchar_ignorecase替换为varchar

使用案例-1

  • 在管理内存中用户信息

1、创建一个项目

2、加入maven坐标

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.4.5</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.4.5</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.4.5</version>
</dependency>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

3、创建应用的配置类

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

    @Bean
    public UserDetailsService userDetailsService() {
        PasswordEncoder encoder = passwordEncoder();
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("admin")
                .password(encoder.encode("admin"))
                .roles("ADMIN", "USER").build());
        manager.createUser(User.withUsername("zs")
                .password(encoder.encode("zs"))
                .roles("USER").build());
        return manager;
    }
}

4、创建类继承WebSecuri tyConfi gurerAdapter

public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService detailsService=null;
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.userDetailsService(detailsService);
    }
}

5、测试

使用案例-2

  • 使用访问数据库,获取认证的用户信息

1、创建项目

2、导入依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.4.5</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.4.5</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.4.5</version>
</dependency>
<!--mysql驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.23</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    <version>2.4.5</version>
</dependency>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

3、创建应用的配置类,创建JdbcUserDetatilsService对象

@Configuration
public class ApplicationConfig {
    //通过spring容器注入 DataSource
    @Autowired
    private DataSource dataSource;

    //创建PasswordEncoder对象
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    //创建JdbcUserDetatilsService对象
    @Bean(name = "jdbcUserDetatilsService")
    public UserDetailsService jdbcUserDetatilsService() {
        System.out.println("===dataSource===" + dataSource);
        PasswordEncoder encoder = passwordEncoder();
        //初始数据源DataSource --- JdbcTemplate对象
        JdbcUserDetailsManager manager = new JdbcUserDetailsManager(dataSource);
        //如果数据库中已经存在账号不添加
        if (!manager.userExists("admin")) {
            manager.createUser(User.withUsername("admin")
                    .password(encoder.encode("admin"))
                    .roles("ADMIN", "USER", "MANAGER").build());
        }
        if (!manager.userExists("zs")) {
            manager.createUser(User.withUsername("zs").
                    password(encoder.encode("zs"))
                    .roles("USER").build());
        }
        if (!manager.userExists("ls")) {
            manager.createUser(User.withUsername("ls")
                    .password(encoder.encode("ls"))
                    .roles("USER", "NORMAL").build());
        }
        return manager;
    }
}

4、创建一个security的配置,自定义安全配置信息。指定JdbcUserDetatilsService类

@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    @Qualifier("jdbcUserDetatilsService")
    private UserDetailsService userDetailsService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        super.configure(http);
        http.userDetailsService(userDetailsService);
    }
}

5、修改application.properties文件 连接数据库。配置数据源DataSource

spring.datasource.url=jdbc:mysql://8.129.121.241:3306/t-ssm?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=xxyy
spring.datasource.password=xxYY11..
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

6.测试

使用案例-3

  • 自定义角色信息(定义”用户、角色、关系“三张表)

1、新建maven项目

2、导入坐标

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.5</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--mybatis启动依赖-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.0</version>
</dependency>
<!--mysql驱动-->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

3、编写application.properties

spring.datasource.url=jdbc:mysql://8.129.*.*:3306/t-ssm?serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.username=******
spring.datasource.password=******
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
#扫描的mapper.xml
mybatis.mapper-locations=classpath:/mapper/*Mapper.xml
#对实体类起别名(简写)
mybatis.type-aliases-package=com.wkcto.entity
#mybatis打印log日志
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

4、Controller、Service、DAO三层

4.1、实体类

  • SysUser类
public class SysUser implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private String realname;
    private boolean isExpired;
    private boolean isLocked;
    private boolean isCredentials;
    private boolean isEnabled;
    private Date createTime;
    private Date loginTime;
    private List<GrantedAuthority> authorities;
// 省略“有参 无参 get set toString”方法
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }
    @Override
    public String getPassword() {
        return password;
    }
    @Override
    public String getUsername() {
        return username;
    }
    @Override
    public boolean isAccountNonExpired() {
        return isExpired;
    }
    @Override
    public boolean isAccountNonLocked() {
        return isLocked;
    }
    @Override
    public boolean isCredentialsNonExpired() {
        return isExpired;
    }
    @Override
    public boolean isEnabled() {
        return isEnabled;
    }
}
  • SysRole类
public class SysRole {
    private Integer id;
    private String name;
    private String memo;
// 省略“有参 无参 get set toString”方法
}

4.2、Dao层

  • SysUserMapper类
@Repository
public interface SysUserMapper {
    int insertSysUser(SysUser user);
    SysUser selectSysUserByUser(String username);
}
  • SysUserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wkcto.mapper.RoleMapper">
    <select id="selectRoleByUser" resultMap="roleMapper">
        select r.id, r.rolename,r.rolememo from sys_user_role ur , sys_role r
        where ur.roleid = r.id and ur.userid=#{userid}
    </select>
    <resultMap id="roleMapper" type="com.wkcto.entity.SysRole">
        <id column="id" property="id"/>
        <result column="rolename" property="name"/>
        <result column="rolememo" property="memo"/>
    </resultMap>
</mapper>
  • RoleMapper类
@Repository
public interface RoleMapper {
    List<SysRole> selectRoleByUser(Integer userId);
}
  • RoleMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wkcto.mapper.RoleMapper">
    <select id="selectRoleByUser" resultMap="roleMapper">
        select r.id, r.rolename,r.rolememo from sys_user_role ur , sys_role r
        where ur.roleid = r.id and ur.userid=#{userid}
    </select>
    <resultMap id="roleMapper" type="com.wkcto.entity.SysRole">
        <id column="id" property="id"/>
        <result column="rolename" property="name"/>
        <result column="rolememo" property="memo"/>
    </resultMap>
</mapper>

4.3、Service层

  • 创建自定义的UserDetatilsService实现类

    ​ 根据username从数据库中查询账户信息SysUser对象,再根据user的id,查询Sys_Role表获取Role信息

@Service
public class JdbcUserDetatilsService implements UserDetailsService {
    @Autowired
    private SysUserMapper userMapper;
    @Autowired
    private RoleMapper roleMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        Logs.msg("loadUserByUsername", username);
        SysUser user = userMapper.selectSysUserByUser(username);
        Logs.msg("selectSysUserByUser", user);
        if (user != null) {
            List<SysRole> roleList = roleMapper.selectRoleByUser(user.getId());
            List<GrantedAuthority> authorities = new ArrayList<>();
            for (SysRole role : roleList) {
                String roleName = role.getName();
                GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + roleName);
                authorities.add(authority);
            }
            user.setAuthorities(authorities);
        }
        Logs.msg("authorities", user);
        return user;
    }
}

4.4、Controller层

  1. IndexController:转发到首页index.html
  2. MyController:连接指定的Controller
  • IndexController
@Controller
public class IndexController {
    @GetMapping("/index")
    public String toIndexHtml(){
        return "forward:/index.html";
    }
}
  • MyController
@RestController
public class MyController {
    @GetMapping(value = "/access/user",produces = "text/html;charset=utf-8")
    public String sayUser(){
        return "zs is user role";
    }
    @GetMapping(value = "/access/read",produces = "text/html;charset=utf-8")
    public String sayRead(){
        return "ls is read";
    }
    @GetMapping(value = "/access/admin",produces = "text/html;charset=utf-8")
    public String sayAdmin(){
        return "admin is user,admin role";
    }
}

5、创建类继承WebSecurityConfigurerAdapter,自定义安全的配置

@Configuration
@EnableWebSecurity
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/index").permitAll()
                .antMatchers("/access/user/**").hasRole("USER")
                .antMatchers("/access/read/**").hasRole("READ")
                .antMatchers("/access/admin/**").hasRole("ADMIN")
                .and()
                .formLogin();
    }
}

6、主类

@MapperScan(value = "com.wkcto.mapper")
@SpringBootApplication
public class UserRoleApplication {
    @Autowired
    private SysUserMapper userMapper;

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

    //    @PostConstruct //在servlet加载之前执行,且只执行一次
    public void jdbcInit() {
        PasswordEncoder encoder = new BCryptPasswordEncoder();
        List<GrantedAuthority> list = new ArrayList();
        Date curDate = new Date();
        GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + "USER");
        list.add(authority);
        SysUser user = new SysUser("zs", encoder.encode("zs"), "张三", true, true, true, true, curDate, curDate, list);
        userMapper.insertSysUser(user);
        System.out.println(user);
    }

    //    @PostConstruct
    public void jdbcInit2() {
        Date curDate = new Date();
        PasswordEncoder encoder = new BCryptPasswordEncoder();
        List<GrantedAuthority> list = new ArrayList<>();
        //参数角色名称,需要以"ROLE_"开头, 后面加上自定义的角色名称
        GrantedAuthority authority = new SimpleGrantedAuthority("ROLE_" + "READ");
        list.add(authority);
        SysUser user = new SysUser(
                "lisi", encoder.encode("ls"), "李四", true, true, true, true, curDate, curDate, list
        );
        userMapper.insertSysUser(user);
        List<GrantedAuthority> list2 = new ArrayList<>();
        GrantedAuthority authority2 = new SimpleGrantedAuthority("ROLE_" + "AMDIN");
        GrantedAuthority authority3 = new SimpleGrantedAuthority("ROLE_" + "USER");
        list.add(authority2);
        list.add(authority3);
        SysUser user2 = new SysUser(
                "admin", encoder.encode("admin"), "管理员", true, true, true, true, curDate, curDate, list2
        );
        userMapper.insertSysUser(user2);
    }
}

7、index.html 首页

  • src/main/resources/static/index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>验证访问</p>
    <a href="/access/user">验证zs</a> <br/>
    <a href="/access/read">验证ls</a> <br/>
    <a href="/access/admin">验证admin</a> <br/>
    <a href="/logout">退出系统</a>
</body>
</html>

8、Sql脚本文件

/*
MySQL Backup
Database: t-ssm
Backup Time: 2021-05-25 16:33:01
*/

SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `t-ssm`.`sys_role`;
DROP TABLE IF EXISTS `t-ssm`.`sys_user`;
DROP TABLE IF EXISTS `t-ssm`.`sys_user_role`;
CREATE TABLE `sys_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rolename` varchar(255) DEFAULT NULL,
  `rolememo` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
CREATE TABLE `sys_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(100) DEFAULT NULL,
  `password` varchar(100) DEFAULT NULL,
  `realname` varchar(200) DEFAULT NULL,
  `isenable` int(11) DEFAULT NULL,
  `islock` int(11) DEFAULT NULL,
  `isexpire` int(11) DEFAULT NULL,
  `iscredentials` int(11) DEFAULT NULL,
  `createtime` date DEFAULT NULL,
  `logintime` date DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;
CREATE TABLE `sys_user_role` (
  `userid` int(11) DEFAULT NULL,
  `roleid` int(11) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
BEGIN;
LOCK TABLES `t-ssm`.`sys_role` WRITE;
DELETE FROM `t-ssm`.`sys_role`;
INSERT INTO `t-ssm`.`sys_role` (`id`,`rolename`,`rolememo`) VALUES (1, 'USER', '普通用户'),(2, 'ADMIN', '管理员'),(3, 'READ', '只读');
UNLOCK TABLES;
COMMIT;
BEGIN;
LOCK TABLES `t-ssm`.`sys_user` WRITE;
DELETE FROM `t-ssm`.`sys_user`;
INSERT INTO `t-ssm`.`sys_user` (`id`,`username`,`password`,`realname`,`isenable`,`islock`,`isexpire`,`iscredentials`,`createtime`,`logintime`) VALUES (1, 'zs', '$2a$10$iEZgNdcXVAfNKfaa/zlD8un7nqW5mASwqPQAFLpluTfpxIN8lamFG', '张三', 1, 1, 1, 1, '2021-05-25', '2021-05-25'),(2, 'ls', '$2a$10$SA8yZU5D3/z9k/RV1SeswORJ6DKj8och26E7E2wInl5IkHvss1oc6', '李四', 1, 1, 1, 1, '2021-05-25', '2021-05-25'),(3, 'admin', '$2a$10$B8zilw4lDibcN8Za8EA3OOsllMwKcLZMPznjQwgyP4BYMPgK9I7ni', '管理员', 1, 1, 1, 1, '2021-05-25', '2021-05-25');
UNLOCK TABLES;
COMMIT;
BEGIN;
LOCK TABLES `t-ssm`.`sys_user_role` WRITE;
DELETE FROM `t-ssm`.`sys_user_role`;
INSERT INTO `t-ssm`.`sys_user_role` (`userid`,`roleid`) VALUES (1, 1),(2, 3),(3, 1),(3, 2);
UNLOCK TABLES;
COMMIT;

自定义登陆界面

步骤

  1. 创建个自定义的登陆界面
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<p>验证访问</p>
    <a href="/access/user">验证zs</a> <br/>
    <a href="/access/read">验证ls</a> <br/>
    <a href="/access/admin">验证admin</a> <br/>
    <a href="/logout">退出系统</a>
</body>
</html>
  1. 修改继承 WebSecurityConfigurerAdapter 类的配置
@Configuration
@EnableWebSecurity
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/index","/mylogin.html","/login","/error.html").permitAll()//将要用到的地址排除
                .antMatchers("/access/user/**").hasRole("USER")
                .antMatchers("/access/read/**").hasRole("READ")
                .antMatchers("/access/admin/**").hasRole("ADMIN")
                .and()
                .formLogin()
                .loginPage("/mylogin.html")//登陆的自定义页面
                .failureUrl("/error.html")//配置错误登陆页面
                .loginProcessingUrl("/login")//登陆时提交的地址
                .and()
                .csrf().disable();//将跨域问题禁用
    }
}

ajax 登陆页面

  1. 引入 jQuery.js 文件,编写ajax登陆界面代码,白名单添加引入的 js 文件 url

    1)在 static 目录中,创建js目录,拷贝 jquery-3.4.1.js 文件

    2)编写 myajax.html 文件,并在文件中引入jquery

    3)加入 ajax 请求处理,并定义 DOM 对象

<head>
    <meta charset="UTF-8">
    <title>myajax</title>
    <!--引入jquery-->
    <script type="text/javascript" src="/js/jquery-3.4.1.js"></script>
    <script type="text/javascript">
        $(function () {
            $("#btnLogin").click(function () {
                alert("click");
                var uname=$("#username").val();
                var upwd=$("#password").val();
                $.ajax({
                    url:"/login",
                    type:"POST",
                    data:{
                        "username":uname,
                        "password":upwd
                    },
                    dataType:"json",
                    success:function (resp) {
                        alert(resp.msg)
                    }
                })
            })
        })
    </script>
</head>
<body>
<h3>自定义Ajax登陆界面</h3>
<div> <!--定义的DOM对象-->
    用户名:<input type="text" id="username" value=""><br>&nbsp;&nbsp;码:<input type="text" id="password" value=""><br>
    <button id="btnLogin">使用ajax登陆</button>
</div>
</body>
  1. 创建 Handler 实现两个不同接口
  • MyFailureHandler
@Component("myFailureHandler")
public class MyFailureHandler implements AuthenticationFailureHandler {
    /*
           参数:

             request : 请求对象
             response:应答对象

             authentication: spring security框架验证用户信息成功后的封装类。

         */
    @Override
    public void onAuthenticationFailure(HttpServletRequest request,
                                        HttpServletResponse response,
                                        AuthenticationException e) throws IOException {
        //当框架验证用户信息失败时执行的方法
        response.setContentType("text/json;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.println("{\"msg\":\"登陆失败!\"}");
        writer.flush();
        writer.close();
    }
}
  • MySuccessHandler
@Component("mySuccessHandler")
public class MySuccessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        response.setContentType("text/json;charset=utf-8");
        PrintWriter pw = response.getWriter();
        pw.write("{\"msg\":\"登陆成功!\"}");
        pw.flush();
        pw.close();
    }
}
  1. 配置文件添加自己创建的handler
@Configuration
@EnableWebSecurity
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private AuthenticationFailureHandler myFailureHandler;
    @Autowired
    private AuthenticationSuccessHandler mysuccessHandler;
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/index","/mylogin.html","/login","/js/**").permitAll()//将要用到的地址排除
                .antMatchers("/access/user/**").hasRole("USER")
                .antMatchers("/access/read/**").hasRole("READ")
                .antMatchers("/access/admin/**").hasRole("ADMIN")
                .and()
                .formLogin()
                .successHandler(mysuccessHandler)//的定义登陆成功Handler
                .failureHandler(myFailureHandler)//的定义登陆失败Handler
                .loginPage("/myajax.html")//登陆的自定义页面
                .loginProcessingUrl("/login")//登陆时提交的地址
                .and()
                .csrf().disable();//将跨域问题禁用
    }
}

使用Json处理数据

  1. 导入依赖

    使用 SpringBoot 就不要添加版本号,否则会版本冲突

  2. 返回结果

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
    // 0 成功,1 失败
    private int code;
    //表示错误码
    private int error;
    //消息文本
    private String msg;
}
  1. 使用 outputStream 输出数据
response.setContentType("text/json;charset=utf-8");
Result result = new Result();
result.setCode(1);
result.setError(1001);
result.setMsg("登陆失败");
ObjectMapper objectMapper = new ObjectMapper();
ServletOutputStream outputStream = response.getOutputStream();
objectMapper.writeValue(outputStream, result);
outputStream.flush();
outputStream.close();

验证码

  1. 因为使用的是 SpringSecurity ,所以将验证码 url 添加到白名单
  2. 代码
@RestController
@RequestMapping("/captcha")
public class CaptChaController {

    //定义一个值,用来生成验证码的图片
    //图像宽度 120像素
    private int width = 120;

    //图像高度 30 像素
    private int height = 30;

    //图片内容在图片的起始位置 12像素
    private int drawY = 20;

    //文字的间隔  18像素
    private int space = 15;

    //验证码有个文字
    private int charCount = 6;

    //验证码的内容数组
    private String chars[] = {"A", "B", "C", "D", "E", "F",
            "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "T", "U", "V", "W",
            "X", "Y", "Z", "1", "2", "3", "4", "5", "6", "7", "8", "9", "0"};

    //定义方法:生成验证码内容。 在一个图片上,写入文字
    @GetMapping("/code")
    public void makeCaptchaCode(HttpServletRequest request, HttpServletResponse response) throws IOException {
        /*
           验证码:需要在内存中绘制一个图片BufferedImage.
           向这个图片中写入文字。 把绘制好内容的图片响应给请求
         */
        //1.创建一个背景透明的图片,使用rgb表示颜色的
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        //2.获取画笔
        Graphics g = image.getGraphics();
        //3.1设置使用画笔是白颜色
        g.setColor(Color.white);
        //3.2给image画板都涂成白色的
        // fillRect(矩形的起始x,矩形的起始y, 矩形的宽度,矩形的高度)
        g.fillRect(0, 0, width, height);
        //画内容
        //4.创建一个字体
        Font font = new Font("宋体", Font.BOLD, 16);
        g.setFont(font);
        g.setColor(Color.black);
        //5.在画布上,写一个文字
        //参数: 文字,x,y坐标
        //g.drawString("中",10,drawY);
        StringBuffer buffer = new StringBuffer("");
        int ran = 0;
        int len = chars.length;
        for (int i = 0; i < charCount; i++) {
            ran = new Random().nextInt(len);
            buffer.append(chars[ran]);
            //绘制文字获取随机颜色
            g.setColor(makeColor());
            g.drawString(chars[ran], (i + 1) * space, drawY);
        }
        //6.绘制干扰线
        for (int m = 0; m < 4; m++) {
            g.setColor(makeColor());
            int dot[] = makeLineDot();
            g.drawLine(dot[0], dot[1], dot[2], dot[3]);
        }
        //把生成的验证码存储到session中
        request.getSession().setAttribute("code", buffer.toString());
        //设置没有缓冲
        response.setHeader("Pragma", "no-cache");
        response.setHeader("Cache-Control", "no-cache");
        response.setDateHeader("Expires", 0);
        response.setContentType("image/png");
        OutputStream out = response.getOutputStream();
        /*
           RenderedImage im, 输出的图像

           String formatName, 图像的格式 jpg,jpeg, png
           ImageOutputStream output 输出到哪
         */
        ImageIO.write(image, "png", out);
        out.flush();
        out.close();
    }
	//取随机颜色
    private Color makeColor() {
        Random random = new Random();
        int r = random.nextInt(255);
        int g = random.nextInt(255);
        int b = random.nextInt(255);
        return new Color(r, g, b);
    }
	//设置干扰线
    private int[] makeLineDot() {
        Random random = new Random();
        int x1 = random.nextInt(width / 2);//起点
        int y1 = random.nextInt(height);//起点
        int x2 = random.nextInt(width);//终点
        int y2 = random.nextInt(height);//终点
        return new int[]{x1, y1, x2, y2};
    }
}
  1. 修改 myajax.html 增加验证码
function changeCode() {
    //new Date目的是浏览器不使用缓存,每次获取新的内容
    var url="/captcha/code?t="+new Date();
    $("#imagecode").attr("src",url);
}
<div >
    用户名:<input type="text" id="username" value=""> <br/>&nbsp;&nbsp;码:<input type="text" id="password" value=""> <br/>
    验证码:<input type="text" id="txtcode" value=""> <br/>
    <!--图像,显示验证码的值 -->
    <img id="imagecode" src="/captcha/code" />
    <a href="javascript:void(0)" onclick="changeCode()">重新获取</a>
    <br/>
    <br/>
    <button id="btnLogin">使用ajax登录</button>
</div>
  1. 增加验证码参数
$(function () {
    $("#btnLogin").click(function () {
        alert("click");
        var uname = $("#username").val();
        var upwd = $("#password").val();
        var txtcode = $("#txtcode").val();
        $.ajax({
            url: "/login",
            type: "POST",
            data: {
                "username": uname,
                "password": upwd,
                "code": txtcode
            },
            dataType: "json",
            success: function (resp) {
                alert(resp.msg)
            }
        })
    })
})

过滤器进行验证Code

image-20210606142100774

  1. 使用的是过滤器,整个 Spring Security 框架就是一个过滤器实现的
  2. 用户发起请求,使用的过滤器:UsernamePasswordAuthenticationFilter
  3. 验证 username 和 password 之前,就应该先验证 code 是否正确,在UsernamePasswordAuthenticationFilter之前增加一个自定义过滤器,验证 session 中的 Code ,如果验证失败,排除异常
    1. 直接实现 Filter 接口
    2. 继承 只执行一次的过滤器

创建一个异常类

  1. 创建异常类继承 AuthenticationException
public class VerificationException extends AuthenticationException {
    public VerificationException(String msg, Throwable cause) {
        super(msg, cause);
    }

    public VerificationException(String msg) {
        super(msg);
    }

    public VerificationException() {
        super("验证错误,请重新输入");
    }
}
  1. 创建过滤器类继承 OncePerRequestFilter
public class VerificationCodeFilter extends OncePerRequestFilter {
    private MyFailureHandler failureHandler = new MyFailureHandler();

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String uri = request.getRequestURI();
        if (!("/login".equals(uri))) {
            filterChain.doFilter(request, response);
        } else {
            try {
                verifcatioinCode(request);
                filterChain.doFilter(request, response);
            } catch (VerificationException e) {
                Result result = new Result();
                result.setCode(1);
                result.setError(1002);
                result.setMsg("验证码错误!!!");
                failureHandler.setResult(result);
                failureHandler.onAuthenticationFailure(request, response, e);
            }

        }
    }

    private void verifcatioinCode(HttpServletRequest request) {
        HttpSession session = request.getSession();
        String requestCode = request.getParameter("code");
        Object attrCode = session.getAttribute("code");
        String sessionCode = "";
        if (attrCode != null) {
            sessionCode = (String) attrCode;
        }
        System.out.println("VerificationCodeFilter  doFilterInternal requestCode:" + requestCode + "|sessionCode:" + sessionCode);
//        if (!StringUtils.isEmpty(sessionCode)) {
//            //session中存在code,就清除
//            session.removeAttribute("code");
//        }
        if (StringUtils.isEmpty(requestCode) || StringUtils.isEmpty(sessionCode) || !requestCode.equals(sessionCode)) {
            throw new VerificationException();
        }
    }
}
  1. 自定义过滤器添加到过滤器链中
@Configuration
@EnableWebSecurity
public class CustomSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private AuthenticationFailureHandler myFailureHandler;
    @Autowired
    private AuthenticationSuccessHandler mysuccessHandler;

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/index", "/mylogin.html", "/login", "/js/**", "/login.jsp").permitAll()//将要用到的地址排除
                .antMatchers("/access/user/**").hasRole("USER")
                .antMatchers("/access/read/**").hasRole("READ")
                .antMatchers("/access/admin/**").hasRole("ADMIN")
                .and()
                .formLogin()
                .successHandler(mysuccessHandler)
                .failureHandler(myFailureHandler)
                .loginPage("/myajax.html")//登陆的自定义页面
                .loginProcessingUrl("/login")//登陆时提交的地址
                .and()
                .csrf().disable()//将跨域问题禁用
                //自定义过滤器添加到过滤器链中
                .addFilterBefore(new VerificationCodeFilter(), UsernamePasswordAuthenticationFilter.class);
    }
}
 
原文:https://www.cnblogs.com/littleleopard/p/14800320.html


相关教程