1. SpringBoot整合SpringSecurity

Spring Security 是 Spring 家族中的一个安全管理框架,提供了权限的解决方案,通过一些简单的配置以及代码,就可以轻松实现。

安全:认证+授权

1.1 导入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>

1.2 启动访问接口

1
2
//启动会在控制台出现
Using generated security password: 98687887-01bf-41b5-bb6e-63009367be0f

同时访问http://localhost:8080/user/findAll ,会出现一个登陆页面

这时候,用户名输入user,密码输入上方控制台打印的密码,即可登录,并且正常访问接口。

1.3 登录的用户名/密码

对登录的用户名/密码进行配置,有三种不同的方式:

  1. 在 application.properties 中进行配置

    1
    2
    spring.security.user.name=admin
    spring.security.user.password=mszlu
  2. 通过 Java 代码配置在内存中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package com.mszlu.union.config;

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

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    //下面这两行配置表示在内存中配置了两个用户
    auth.inMemoryAuthentication()
    .withUser("admin").roles("admin").password("$2a$10$2UaOufuypWR1TASuso2S6.u6TGL7nuAGCsb4RZ5X2SMEuelwQBToO")
    .and()
    .withUser("user").roles("user").password("$2a$10$2UaOufuypWR1TASuso2S6.u6TGL7nuAGCsb4RZ5X2SMEuelwQBToO");
    }
    @Bean
    PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
    }

    public static void main(String[] args) {
    String mszlu = new BCryptPasswordEncoder().encode("mszlu");
    System.out.println(mszlu);
    }
    }
  1. 通过 Java 从数据库中加载

1.4 登录配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests() //开启登录认证
.antMatchers("/user/findAll").hasRole("admin") //访问接口需要admin的角色
.antMatchers("/login").permitAll()
.anyRequest().authenticated() // 其他所有的请求 只需要登录即可
.and().formLogin()
.loginPage("/login.html") //自定义的登录页面
.loginProcessingUrl("/login") //登录处理接口
.usernameParameter("username") //定义登录时的用户名的key 默认为username
.passwordParameter("password") //定义登录时的密码key,默认是password
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("success");
out.flush();
}
}) //登录成功处理器
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("fail");
out.flush();
}
}) //登录失败处理器
.permitAll() //通过 不拦截,更加前面配的路径决定,这是指和登录表单相关的接口 都通过
.and().logout() //退出登录配置
.logoutUrl("/logout") //退出登录接口
.logoutSuccessHandler(new LogoutSuccessHandler() {
@Override
public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
PrintWriter out = httpServletResponse.getWriter();
out.write("logout success");
out.flush();
}
}) //退出登录成功 处理器
.permitAll() //退出登录的接口放行
.and()
.httpBasic()
.and()
.csrf().disable(); //csrf关闭 如果自定义登录 需要关闭

}

1.4.1 自定义登录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>自定义登录页面</title>
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
</head>
<body>
<div id="app">
用户名: <el-input v-model="username" placeholder="请输入内容"></el-input>
密码: <el-input v-model="password" placeholder="请输入内容"></el-input>
<el-button type="success" @click="login()">提交</el-button>
</div>
<!-- 引入组件库 -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>

<script>
new Vue({
el: "#app",
data:{
username:"",
password:""
},
methods:{
login(){
axios.post("/login?username="+this.username+"&password="+this.password).then((res)=>{
if (res.data == "success"){
this.$message.success("登录成功");
}else{
this.$message.error("用户名或密码错误");
}
})
}
}
});
</script>
</body>
</html>

1.5 数据库访问认证和授权

1.5.1 登录认证

  1. 表结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    CREATE TABLE `admin_user`  (
    `id` bigint(0) NOT NULL AUTO_INCREMENT,
    `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    `create_time` bigint(0) NOT NULL,
    PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;

    INSERT INTO `admin_user`(`id`, `username`, `password`, `create_time`) VALUES (1, 'admin', '$2a$10$2UaOufuypWR1TASuso2S6.u6TGL7nuAGCsb4RZ5X2SMEuelwQBToO', 1622711132975);

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package com.mszlu.union.pojo;

    import lombok.Data;

    @Data
    public class AdminUser {

    private Long id;

    private String username;

    private String password;

    private Long createTime;
    }

    1
    2
    3
    4
    5
    6
    7
    8
    package com.mszlu.union.mapper;

    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.mszlu.union.pojo.AdminUser;

    public interface AdminUserMapper extends BaseMapper<AdminUser> {
    }

  1. 实现UserDetailService接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    package com.mszlu.union.security;

    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.mszlu.union.mapper.AdminUserMapper;
    import com.mszlu.union.pojo.AdminUser;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.GrantedAuthority;
    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 java.util.ArrayList;
    import java.util.List;

    @Component
    public class SecurityUserService implements UserDetailsService {
    @Autowired
    private AdminUserMapper adminUserMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    LambdaQueryWrapper<AdminUser> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(AdminUser::getUsername,username).last("limit 1");
    AdminUser adminUser = this.adminUserMapper.selectOne(queryWrapper);
    if (adminUser == null){
    throw new UsernameNotFoundException("用户名不存在");
    }
    List<GrantedAuthority> authorityList = new ArrayList<>();
    UserDetails userDetails = new User(username,adminUser.getPassword(),authorityList);
    return userDetails;
    }
    }

  1. 在配置中添加使用UserDetailService

    1
    2
    3
    4
    5
    @Override
    protected void configure(HttpSecurity http) throws Exception {
    http.userDetailsService(securityUserService);
    //...
    }

1.5.2 授权

  1. 权限相关表结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    DROP TABLE IF EXISTS `role`;
    CREATE TABLE `role` (
    `id` int(0) NOT NULL AUTO_INCREMENT,
    `role_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    `role_desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    `role_keyword` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;

    -- ----------------------------
    -- Records of role
    -- ----------------------------
    INSERT INTO `role` VALUES (1, '管理员', '管理员', 'ADMIN');
    INSERT INTO `role` VALUES (2, '运营', '运营部门', 'BUSINESS');

    DROP TABLE IF EXISTS `user_role`;
    CREATE TABLE `user_role` (
    `id` bigint(0) NOT NULL AUTO_INCREMENT,
    `user_id` bigint(0) NOT NULL,
    `role_id` int(0) NOT NULL,
    PRIMARY KEY (`id`) USING BTREE,
    INDEX `user_id`(`user_id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;

    -- ----------------------------
    -- Records of user_role
    -- ----------------------------
    INSERT INTO `user_role` VALUES (1, 1, 1);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.mszlu.union.pojo;

    import lombok.Data;

    @Data
    public class Role {
    private Integer id;

    private String roleName;

    private String roleDesc;

    private String roleKeyword;
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    DROP TABLE IF EXISTS `permission`;
    CREATE TABLE `permission` (
    `id` int(0) NOT NULL AUTO_INCREMENT,
    `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    `desc` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    `permission_keyword` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
    PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;

    -- ----------------------------
    -- Records of permission
    -- ----------------------------
    INSERT INTO `permission` VALUES (1, '查询全部', '查询全部', 'USER_FINDALL', '/user/findAll');
    INSERT INTO `permission` VALUES (2, '年龄查询', '年龄查询', 'USER_FINDAGE', '/user/findAge');

    DROP TABLE IF EXISTS `role_permission`;
    CREATE TABLE `role_permission` (
    `id` bigint(0) NOT NULL AUTO_INCREMENT,
    `role_id` int(0) NOT NULL,
    `permission_id` int(0) NOT NULL,
    PRIMARY KEY (`id`) USING BTREE,
    INDEX `role_id`(`role_id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic;

    -- ----------------------------
    -- Records of role_permission
    -- ----------------------------
    INSERT INTO `role_permission` VALUES (1, 1, 1);
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package com.mszlu.union.pojo;

    import lombok.Data;

    @Data
    public class Permission {
    private Integer id;

    private String name;

    private String desc;

    private String permissionKeyword;

    private String path;
    }

  1. 在UserDetailService的接口实现中,查询用户的权限

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    package com.mszlu.union.security;

    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.mszlu.union.mapper.AdminUserMapper;
    import com.mszlu.union.mapper.PermissionMapper;
    import com.mszlu.union.mapper.RoleMapper;
    import com.mszlu.union.pojo.AdminUser;
    import com.mszlu.union.pojo.Permission;
    import com.mszlu.union.pojo.Role;
    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 java.util.ArrayList;
    import java.util.List;

    @Component
    public class SecurityUserService implements UserDetailsService {
    @Autowired
    private AdminUserMapper adminUserMapper;
    @Autowired
    private RoleMapper roleMapper;
    @Autowired
    private PermissionMapper permissionMapper;
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    LambdaQueryWrapper<AdminUser> queryWrapper = new LambdaQueryWrapper<>();
    queryWrapper.eq(AdminUser::getUsername,username).last("limit 1");
    AdminUser adminUser = this.adminUserMapper.selectOne(queryWrapper);
    if (adminUser == null){
    throw new UsernameNotFoundException("用户名不存在");
    }
    List<GrantedAuthority> authorityList = new ArrayList<>();
    //查询角色和角色对应的权限 并赋予当前的登录用户,并告知spring security框架
    List<Role> roleList = roleMapper.findRoleListByUserId(adminUser.getId());
    for (Role role : roleList) {
    List<Permission> permissionList = permissionMapper.findPermissionByRole(role.getId());
    authorityList.add(new SimpleGrantedAuthority("ROLE_"+role.getRoleKeyword()));
    for (Permission permission : permissionList) {
    authorityList.add(new SimpleGrantedAuthority(permission.getPermissionKeyword()));
    }
    }
    UserDetails userDetails = new User(username,adminUser.getPassword(),authorityList);
    return userDetails;
    }
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    package com.mszlu.union.mapper;

    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.mszlu.union.pojo.Permission;
    import com.mszlu.union.pojo.Role;

    import java.util.List;

    public interface PermissionMapper extends BaseMapper<Permission> {


    List<Permission> findPermissionByRole(Integer roleId);
    }


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package com.mszlu.union.mapper;

    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.mszlu.union.pojo.Role;

    import java.util.List;

    public interface RoleMapper extends BaseMapper<Role> {

    List<Role> findRoleListByUserId(Long userId);
    }

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <?xml version="1.0" encoding="UTF-8" ?>
    <!--MyBatis配置文件-->
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

    <mapper namespace="com.mszlu.union.mapper.PermissionMapper">
    <resultMap id="perMap" type="com.mszlu.union.pojo.Permission">
    <id column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="desc" property="desc"/>
    <result column="permission_keyword" property="permissionKeyword"/>
    <result column="path" property="path"/>
    </resultMap>

    <select id="findPermissionByRole" parameterType="int" resultMap="perMap">
    select * from permission where id in (select permission_id from role_permission where role_id=#{roleId})
    </select>
    </mapper>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    <?xml version="1.0" encoding="UTF-8" ?>
    <!--MyBatis配置文件-->
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

    <mapper namespace="com.mszlu.union.mapper.RoleMapper">
    <resultMap id="roleMap" type="com.mszlu.union.pojo.Role">
    <id column="id" property="id"/>
    <result column="role_name" property="roleName"/>
    <result column="role_desc" property="roleDesc"/>
    <result column="role_keyword" property="roleKeyword"/>
    </resultMap>

    <select id="findRoleListByUserId" parameterType="long" resultMap="roleMap">
    select * from role where id in (select role_id from user_role where user_id=#{userId})
    </select>
    </mapper>
  1. 在接口上配置权限

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    @GetMapping("findAll")
    @PreAuthorize("hasAuthority('USER_FINDALL')")
    public List<User> findAll(){
    return userService.findAll();
    }

    @GetMapping("findAge")
    @PreAuthorize("hasAuthority('USER_FINDAGE')")
    public List<User> findAge(){
    return userService.findAge();
    }

    @GetMapping("findById")
    @PreAuthorize("hasRole('ADMIN')")
    public User findById(@RequestParam("id") Long id){
    return userService.findById(id);
    }
  1. 在配置上开启权限认证

    1
    2
    3
    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {}

1.5.3 另一种方式进行授权

  1. 修改配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
      @Override
    protected void configure(HttpSecurity http) throws Exception {
    // http.userDetailsService(securityUserService);
    http.authorizeRequests() //开启登录认证
    // .antMatchers("/user/findAll").hasRole("admin") //访问接口需要admin的角色
    .antMatchers("/login").permitAll()
    //使用访问控制 自己实现service处理,会接收两个参数
    .anyRequest().access("@authService.auth(request,authentication)")
    // .anyRequest().authenticated() // 其他所有的请求 只需要登录即可
    }
  1. 编写AuthService的auth方法、

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    package com.mszlu.union.service;

    import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
    import com.mszlu.union.mapper.AdminUserMapper;
    import com.mszlu.union.mapper.PermissionMapper;
    import com.mszlu.union.mapper.RoleMapper;
    import com.mszlu.union.mapper.UserMapper;
    import com.mszlu.union.pojo.AdminUser;
    import com.mszlu.union.pojo.Permission;
    import com.mszlu.union.pojo.Role;
    import com.mszlu.union.security.MySimpleGrantedAuthority;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.stereotype.Service;

    import javax.servlet.http.HttpServletRequest;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;

    @Service
    public class AuthService {

    @Autowired
    private AdminUserMapper adminUserMapper;
    @Autowired
    private RoleMapper roleMapper;
    @Autowired
    private PermissionMapper permissionMapper;

    public boolean auth(HttpServletRequest request, Authentication authentication){
    String requestURI = request.getRequestURI();
    Object principal = authentication.getPrincipal();
    if (principal == null || "anonymousUser".equals(principal)){
    //未登录
    return false;
    }
    UserDetails userDetails = (UserDetails) principal;
    Collection<? extends GrantedAuthority> authorities = userDetails.getAuthorities();
    for (GrantedAuthority authority : authorities) {
    MySimpleGrantedAuthority grantedAuthority = (MySimpleGrantedAuthority) authority;
    String[] paths = StringUtils.split(requestURI, "?");
    if (paths[0].equals(grantedAuthority.getPath())){
    return true;
    }
    }
    return false;
    }
    }

  1. 修改UserDetailService中 授权的实现,实现自定义的授权类,增加path属性

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    package com.mszlu.union.security;

    import org.springframework.security.core.GrantedAuthority;

    public class MySimpleGrantedAuthority implements GrantedAuthority {
    private String authority;
    private String path;

    public MySimpleGrantedAuthority(){}

    public MySimpleGrantedAuthority(String authority){
    this.authority = authority;
    }

    public MySimpleGrantedAuthority(String authority,String path){
    this.authority = authority;
    this.path = path;
    }

    @Override
    public String getAuthority() {
    return authority;
    }

    public String getPath() {
    return path;
    }
    }