在 WebSecurityConfig 添加@EnableGlobalMethodSecurity 注解开启方法的访问权限, 代码如下:
@EnableWebSecurity //是 Spring Security 用于启用 Web 安全的注解 @EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级安全验证 
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 
         //... 
}
代码解释:
prePostEnabled=true 会解锁@PreAuthorize 和@PostAuthorize 两个注解, 
@PreAuthorize 注解会在方法执行前进行验证,而@PostAuthorize 注解在方法执行后进行验证。
在控制层添加访问接口
在 UserController 类中增加方法的访问权限:
@RestController 
public class UserController { 
            @Autowired 
            private UserInfoService userInfoService; 
            @GetMapping("/getUser") 
            public UserInfo getUser(@RequestParam String username){ 
                        return userInfoService.getUserInfo(username); 
}
            @PreAuthorize("hasAnyRole('user')") // 只能 user 角色才能访问该方法 
            @GetMapping("/user") 
            public String user(){ 
                        return "hello,user"; 
            }
            @PreAuthorize("hasAnyRole('admin')") // 只能 admin 角色才能访问该方法 
            @GetMapping("/admin") 
            public String admin(){ 
                        return "hello,admin"; 
            } 
}
代码解释:
@PreAuthorize("hasAnyRole('user')")注解表示访问该方法需要 USER 角色。
密码加密保存
       上文中的用户密码都是手动在数据库添加的,所以数据库中是明文显示,在实际开发中, 都是需要加密保存的。下面模拟注册用户,加密保存密码:
修改 mapper 接口
在 UserMapper 接口中添加插入用户,代码如下:
@Mapper 
@Repository 
public interface UserMapper { 
               //... 
                @Insert("insert into user(username, password, role) value(#{username}, #{password}, #{role})") 
                int insertUserInfo(UserInfo userInfo); 
}
修改 service 类
在 UserInfoService 类中添加插入方法,并且密码要加密保护,代码如下:

@Service 
public class UserInfoService { 
            // ... 
             @Autowired 
             private PasswordEncoder passwordEncoder; 
            // ... 
             public int insertUser(UserInfo userInfo){ 
                 /* 加密密码*/ 
userInfo.setPassword(passwordEncoder.encode(userInfo.getPassword())); 
                     return userMapper.insertUserInfo(userInfo); 
      } 
}
修改 controoler

在 UserController 类中添加插入用户接口,代码如下:
@RestController 
public class UserController{ 
      //... 
         @PostMapping("/addUser") 
       public int addUser(@RequestBody UserInfo userInfo){ 
           return userInfoService.insertUser(userInfo); 
       } 
}
添加失败,响应的状态码显示 401 Unauthorized, 说明无权限,需要登录,但注册用户是不用登录的,所以需要给注册用户释放权限。 
       修改 WebSecurityConfig 配置类,重写 configure(HttpSecurity http)方法,配置允许注册用户的请求访问:
@EnableWebSecurity 
@EnableGlobalMethodSecurity(prePostEnabled = true) 
public class WebSecurityConfig extends WebSecurityConfigurerAdapter { 
    //... 
       @Override 
       protected void configure(HttpSecurity http) throws Exception { 
              http
                      .authorizeRequests() 
// 允许 post 请求/addUser,而无需认证 
                      .antMatchers(HttpMethod.POST, "/addUser").permitAll() 
                      .anyRequest().authenticated() // 所有请求都需要验证
                      .and() 
                      .formLogin() // 使用默认的登录页面 
                      .and() 
                      .csrf().disable(); 
// post 请求要关闭 csrf 验证,不然访问报错;实际开发中开启,需要前端配合传递其他参数 
}}
使用加密密码登录

       使用加密密码登录,需要修改 CustomUserDetailsService 类,之前从数据库拿到明文密码后需要加密,现在数据库里面的密码已经加密了,就不用加密了,代码如下:
@Component 
public class MyUserDatailService implements UserDetailsService { 
           //... 
           @Override 
           public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 
           // ... 
           return new User( 
                      userInfo.getUsername(), 
                      // 数据库密码已加密,不用再加密 
                      userInfo.getPassword(), 
                      authorities 
           ); 
     } 
}
       角色继承实际上是一个很常见的需求,因为大部分公司治理可能都是金字塔形的,上司 可能具备下属的部分甚至所有权限,这一现实场景,反映到我们的代码中,就是角色继承了。 Spring Security 中为开发者提供了相关的角色继承解决方案,只需要开发者在配置类中提供 一个 RoleHierarchy 即可,代码如下:
@Bean 
RoleHierarchy roleHierarchy() { 
      RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl(); 
      String hierarchy = "ROLE_dba > ROLE_admin \n ROLE_admin > ROLE_user"; 
      roleHierarchy.setHierarchy(hierarchy); 
      return roleHierarchy; 
}
      在这里我们提供了一个 RoleHierarchy 接口的实例,使用字符串来描述了角色之间的继承关系, ROLE_dba 具备 ROLE_admin 的所有权限,而 ROLE_admin 则具备 ROLE_user的所有权限。提供了这个 Bean 之后,以后所有具备 ROLE_user 角色才能访问的资源,ROLE_dba 和 ROLE_admin 也都能访问,具备 ROLE_amdin 角色才能访问的资源,ROLE_dba 也能访问。
<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.dsecurity</groupId>
    <artifactId>dsecurity</artifactId>
    <version>0.0.1-SNAPSHOT</version>

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

    <properties>
        <!-- 声明项目配置依赖编码格式为 utf-8 -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <fastjson.version>1.2.24</fastjson.version>
    </properties>

    <dependencies>

        <!--Spring Data Jpa依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</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-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
            <version>8.0.13</version><!--$NO-MVN-MAN-VER$ -->
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

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

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
server.port =8089

spring.datasource.url=jdbc:mysql://localhost:3306/springbootdb?serverTimezone=UTC&autoReconnect=true
spring.datasource.username=root
spring.datasource.password=admin
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

logging.level.com.example.bdatabaserole.mapper=debug

mybatis.mapper-locations=classpath:mappers/*.xml
mybatis.type-aliases-package=com.tszr.mapper
mybatis.configuration.map-underscore-to-camel-case=true

spring.jpa.database=MySQL
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
<?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">
<!--命名空间必须和UserMapper全类名相同 -->
<mapper namespace="com.tszr.mapper.UserMapper">
    <select id="getUserByUsername"
        resultType="com.tszr.entity.User">
        select * from user where username = #{username};
    </select>
    <select id="getRolesById" resultType="com.tszr.entity.Role">
        select * from role where id in(select rid from user_role where uid=#{uid});
    </select>
</mapper>
package com.tszr.entity;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.JoinColumn;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.Table;

@Entity
@Table(name = "role")
public class Role {
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNameZh() {
        return nameZh;
    }

    public void setNameZh(String nameZh) {
        this.nameZh = nameZh;
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @ManyToOne(cascade=CascadeType. REFRESH ,optional= false)
    @ JoinColumn (name =  "user_id" )
    private int id;
    
    @Column(name = "name", unique = true, nullable = false, length = 64)
    private String name;
        
    @Column(name = "nameZh", nullable = false, length = 64)
    private String nameZh;
}
package com.tszr.entity;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import com.fasterxml.jackson.annotation.JsonIgnore;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

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

@Entity
@Table(name = "user")
public class User implements Serializable, UserDetails {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    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 List<Role> getRoleList() {
        return roleList;
    }

    public void setRoleList(List<Role> roleList) {
        this.roleList = roleList;
    }

    @Column(name = "username", unique = true, nullable = false, length = 64)
    private String username;

    @Column(name = "password", nullable = false, length = 64)
    private String password;
    
    @JsonIgnore
    private List<Role> roleList;

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        for (Role role : roleList) {
            // 数据库role表字段中是以ROLE_开头的,所以此处不必再加ROLE_
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }

    /**
     * 指示用户的账户是否已过期。无法验证过期的账户。 如果用户的账户有效(即未过期),则返回true,如果不在有效就返回false
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    /**
     * 指示用户是锁定还是解锁。无法对锁定的用户进行身份验证。 如果用户未被锁定,则返回true,否则返回false
     */
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    /**
     * 指示用户的凭证(密码)是否已过期。过期的凭证阻止身份验证 如果用户的凭证有效(即未过期),则返回true 如果不在有效(即过期),则返回false
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    /**
     * 指示用户是启用还是禁用。无法对禁用的用户进行身份验证 如果启用了用户,则返回true,否则返回false
     */
    @Override
    public boolean isEnabled() {
        return true;
    }
}
package com.tszr.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.springframework.stereotype.Repository;

import com.tszr.entity.Role;
import com.tszr.entity.User;

@Mapper
@Repository
public interface UserMapper {
//  @Select("select * from user where username = #{username}")
    User getUserByUsername(String username);

    List<Role> getRolesById(int id);

    // 添加用户
    @Insert("insert into user(username, password) value(#{username}, #{password})")
    int insertUserInfo(User user);
}
package com.tszr.services;

import org.springframework.stereotype.Service;

import com.tszr.entity.User;
import com.tszr.mapper.UserMapper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;

@Service
public class UserInfoService {
    @Autowired
    private UserMapper userMapper;
    @Autowired
    private PasswordEncoder passwordEncoder;

    public int insertUser(User userInfo) {
        // 加密密码
        userInfo.setPassword(passwordEncoder.encode(userInfo.getPassword()));
        return userMapper.insertUserInfo(userInfo);
    }

    public User getUserInfo(String username) {
        return userMapper.getUserByUsername(username);
    }
}
package com.tszr.services;

import com.tszr.entity.User;
import com.tszr.mapper.UserMapper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;

public class CustomUserDetailsService implements UserDetailsService {
    @Autowired
    private UserInfoService userInfoService;
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 通过用户名从数据库获取用户信息
        User userInfo = userInfoService.getUserInfo(username);
        if (userInfo == null) {
            throw new UsernameNotFoundException("用户不存在");
        }
        userInfo.setRoleList(userMapper.getRolesById(userInfo.getId()));
        return userInfo;
    }
}
package com.tszr.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.prepost.PreAuthorize;

import com.tszr.entity.User;
import com.tszr.services.UserInfoService;

@RestController
public class UserController {
    @Autowired
    private UserInfoService userInfoService;

    // 添加
    @PostMapping("/addUser")
    public int addUser(@RequestBody User userInfo) {
        return userInfoService.insertUser(userInfo);
    }

    @GetMapping("/getUser")
    public User getUser(@RequestParam String username) {
        return userInfoService.getUserInfo(username);
    }

    @PreAuthorize("hasAnyRole('user')") // 只能user角色才能访问该方法
    @GetMapping("/user")
    public String user() {
        return "hello,user";
    }

    @PreAuthorize("hasAnyRole('dba','admin')") // dba\admin角色可以访问该方法
    @GetMapping("/db")
    public String dba() {
        return "hello,dba,admin";
    }

    @PreAuthorize("hasAnyRole('admin')") // 只能admin角色才能访问该方法
    @GetMapping("/admin")
    public String admin() {
        return "hello,admin";
    }
}
package com.tszr.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
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.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 com.tszr.services.CustomUserDetailsService;

@EnableWebSecurity // 是Spring Security用于启用Web安全的注解
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启方法级安全验证
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private CustomUserDetailsService userDatailService;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().antMatchers(HttpMethod.POST, "/addUser").permitAll() // 允许post请求/add-user,而无需认证
                .anyRequest().authenticated() // 所有请求都需要验证
                .and().formLogin() // 使用默认的登录页面
                .and().csrf().disable();// post请求要关闭csrf验证,不然访问报错;实际开发中开启,需要前端配合传递其他参数
    }

    /**
     * 指定加密方式
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        // 使用BCrypt加密密码
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        // 从数据库读取的用户进行身份认证
        auth.userDetailsService(userDatailService).passwordEncoder(passwordEncoder());
    }

}
package com.tszr;

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

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