1 基本概念

1、两个重要的概念

  • 认证:通过用户名和密码成功登陆系统后,让系统得到当前用户的角色身份。
  • 授权:系统根据当前用户的角色,给其授予对应可以操作的权限资源。

2、完成权限管理需要的三个对象

  • 用户:主要包含用户名、密码和当前用户的角色信息,可实现认证操作。
  • 角色:主要包含角色名称、角色描述和当前角色拥有的权限信息,可实现授权操作。
  • 权限:主要包含当前权限名称、URL地址等信息,可实现动态展示菜单。
  • 三者之间的关系:用户↔角色↔权限。

3、Spring Security

  • Spring Security是Spring采用AOP思想,基于Servlet过滤器实现的安全框架。它提供了完善的认证机制和方法级别的授权功能。

2 基本使用

1、创建项目

  • 填写项目基本信息:

图片说明

  • 添加依赖:

图片说明

  • 项目存放路径:

图片说明

  • 添加druid依赖:

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.1.21</version>
    </dependency>

    2、设计数据库表

  • user

图片说明

  • role

图片说明

  • user_role

图片说明

3、实体类

  • User

    package com.xianhuii.entity;
    
    import lombok.Data;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.List;
    
    @Data
    public class User implements UserDetails {
        private Integer id;
        private String username;
        private String password;
        private Boolean enabled;
        private Boolean locked;
        private List<Role> roles;
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            List<SimpleGrantedAuthority> authorities = new ArrayList<>();
            for (Role role : roles) {
                authorities.add(new SimpleGrantedAuthority(role.getName()));
            }
            return authorities;
        }
    
        @Override
        public String getPassword() {
            return password;
        }
    
        @Override
        public String getUsername() {
            return username;
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return !locked;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return enabled;
        }
    
    }
  • Role

    package com.xianhuii.entity;
    
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Role {
        private Integer id;
        private String name;
        private String nameZh;
    }

    4、DAO层

  • application.yaml

    server:
      port: 8080
    spring:
      datasource:
        type: com.alibaba.druid.pool.DruidDataSource
        url: jdbc:mysql://localhost:3306/spring-security?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
        username: root
        password: root
    mybatis:
      mapper-locations: classpath:/mapper/*.xml
      configuration:
        map-underscore-to-camel-case: true
  • UserMapper

    package com.xianhuii.mapper;
    
    import com.xianhuii.entity.Role;
    import com.xianhuii.entity.User;
    
    import java.util.List;
    
    public interface UserMapper {
        User loadUserByUsername(String username);
        List<Role> getUserRolesByUid(Integer id);
    }
  • UserMapper.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.xianhuii.mapper.UserMapper">
        <!--User loadUserByUsername(String username)-->
        <select id="loadUserByUsername" resultType="com.xianhuii.entity.User">
            select * from user where username = #{username}
        </select>
    
        <!--List<Role> getUserRolesByUid(Integer id)-->
        <select id="getUserRolesByUid" resultType="com.xianhuii.entity.Role">
            select * from role r, user_role ur where r.id = ur.rid and ur.uid = #{id}
        </select>
    
    </mapper>

    5、Service层

  • UserService

    package com.xianhuii.service;
    
    import com.xianhuii.entity.User;
    import com.xianhuii.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;
    import org.springframework.stereotype.Service;
    
    @Service
    public class UserService implements UserDetailsService {
        @Autowired
        UserMapper userMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            User user = userMapper.loadUserByUsername(username);
            if (user == null) {
                throw new UsernameNotFoundException("账户不存在!");
            }
            user.setRoles(userMapper.getUserRolesByUid(user.getId()));
            return user;
        }
    
    }

    6、配置Spring Security

      package com.xianhuii.config;
    
      import com.fasterxml.jackson.databind.ObjectMapper;
      import com.xianhuii.service.UserService;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.security.authentication.*;
      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.WebSecurityConfigurerAdapter;
      import org.springframework.security.core.Authentication;
      import org.springframework.security.core.AuthenticationException;
      import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
      import org.springframework.security.crypto.password.PasswordEncoder;
      import org.springframework.security.web.authentication.AuthenticationFailureHandler;
      import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
      import org.springframework.security.web.authentication.logout.LogoutHandler;
      import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
    
      import javax.servlet.ServletException;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      import java.io.PrintWriter;
      import java.util.HashMap;
      import java.util.Map;
    
      @Configuration
      @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
      public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
          @Autowired
          UserService userService;
    
          // 配置密码加密规则
          @Bean
          PasswordEncoder passwordEncoder() {
              return new BCryptPasswordEncoder(10);
          }
    
          // 配置认证规则
          @Override
          protected void configure(AuthenticationManagerBuilder auth) throws Exception {
              auth.userDetailsService(userService);
          }
    
          // 配置授权规则
          @Override
          protected void configure(HttpSecurity http) throws Exception {
              http.authorizeRequests()
                      .antMatchers("/admin/**")
                      .hasRole("ADMIN")
                      .antMatchers("/user/**")
                      .access("hasAnyRole('ADMIN', 'USER')")
                      .antMatchers("/db/**")
                      .access("hasRole('ADMIN') and hasRole('DBA')")
                      .anyRequest()
                      .authenticated()
                      .and()
                      .formLogin()
                      .loginProcessingUrl("/login")
                      .permitAll()
                      .and()
                      .formLogin()
                      .loginPage("/login_page")
                      .loginProcessingUrl("/login")
                      .usernameParameter("name")
                      .passwordParameter("passwd")
                      .successHandler(new AuthenticationSuccessHandler() {
                          @Override
                          public void onAuthenticationSuccess(HttpServletRequest request,
                                                              HttpServletResponse response,
                                                              Authentication authentication) throws IOException, ServletException {
                              Object principal = authentication.getPrincipal();
                              response.setContentType("application/json;charset=utf-8");
                              PrintWriter writer = response.getWriter();
                              response.setStatus(200);
                              Map<String, Object> map = new HashMap<>();
                              map.put("status", 200);
                              map.put("msg", principal);
                              ObjectMapper om = new ObjectMapper();
                              writer.write(om.writeValueAsString(map));
                              writer.flush();
                              writer.close();
                          }
                      })
                      .failureHandler(new AuthenticationFailureHandler() {
                          @Override
                          public void onAuthenticationFailure(HttpServletRequest request,
                                                              HttpServletResponse response,
                                                              AuthenticationException e) throws IOException, ServletException {
                              response.setContentType("application/json;charset=utf-8");
                              PrintWriter writer = response.getWriter();
                              response.setStatus(401);
                              Map<String, Object> map = new HashMap<>();
                              map.put("status", 401);
                              if (e instanceof LockedException) {
                                  map.put("msg", "账户被锁定,登录失败!");
                              } else if (e instanceof BadCredentialsException) {
                                  map.put("msg", "账户名或密码输入错误,登录失败!");
                              } else if (e instanceof DisabledException) {
                                  map.put("msg", "账户被禁用,登录失败!");
                              } else if (e instanceof AccountExpiredException) {
                                  map.put("msg", "账户已过期,登录失败!");
                              } else if (e instanceof CredentialsExpiredException) {
                                  map.put("msg", "密码已过期,登录失败!");
                              } else {
                                  map.put("msg", "登录失败!");
                              }
                              ObjectMapper om = new ObjectMapper();
                              writer.write(om.writeValueAsString(map));
                              writer.flush();
                              writer.close();
                          }
                      })
                      .permitAll()
                      .and()
                      .logout()
                      .logoutUrl("/logout")
                      .clearAuthentication(true)
                      .invalidateHttpSession(true)
                      .addLogoutHandler(new LogoutHandler() {
                          @Override
                          public void logout(HttpServletRequest request,
                                             HttpServletResponse response,
                                             Authentication authentication) {
    
                          }
                      })
                      .logoutSuccessHandler(new LogoutSuccessHandler() {
                          @Override
                          public void onLogoutSuccess(HttpServletRequest request,
                                                      HttpServletResponse response,
                                                      Authentication authentication) throws IOException, ServletException {
                              response.sendRedirect("/login_page");
                          }
                      })
                      .and()
                      .csrf()
                      .disable();
          }
      }