SpringSecurity

2022/3/24 java框架

# 1. 快速入门

  1. 新建一个SpringBoot项目

  2. 导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    
  3. 随机访问一个controller,用户名为user,密码会在控制台打印

# 2. 认证

# 2.1 工具类

  1. 导入依赖

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.79</version>
    </dependency>
    <dependency>
        <groupId>com.auth0</groupId>
        <artifactId>java-jwt</artifactId>
        <version>3.4.0</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>com.baomidou</groupId>
        <artifactId>mybatis-plus-boot-starter</artifactId>
        <version>3.5.1</version>
    </dependency>
    
  2. redis配置类

    @Configuration
    public class RedisConfig {
        @Bean
        public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
            RedisTemplate<Object, Object> template = new RedisTemplate<>();
            template.setConnectionFactory(connectionFactory);
    
            FastJsonRedisSerializer serializer = new FastJsonRedisSerializer(Object.class);
            template.setKeySerializer(new StringRedisSerializer());
            template.setValueSerializer(serializer);
    
            template.setHashKeySerializer(new StringRedisSerializer());
            template.setHashValueSerializer(serializer);
    
            template.afterPropertiesSet();
            return template;
        }
    }
    
  3. redis使用fastjson进行序列化

    public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
    
        public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");
    
        private Class<T> clazz;
    
        static {
            ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        }
    
        public FastJsonRedisSerializer(Class<T> clazz) {
            super();
            this.clazz = clazz;
        }
    
        @Override
        public byte[] serialize(T t) throws SerializationException {
            if (t == null) {
                return new byte[0];
            }
            return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
        }
    
        @Override
        public T deserialize(byte[] bytes) throws SerializationException {
            if (bytes == null || bytes.length <= 0) {
                return null;
            }
            String str = new String(bytes, DEFAULT_CHARSET);
    
            return JSON.parseObject(str, clazz);
        }
    
    
        protected JavaType getJavaType(Class<?> clazz)
        {
            return TypeFactory.defaultInstance().constructType(clazz);
        }
    }
    
  4. 统一返回对象

    @Data
    public class R {
        private Integer code;
        private String message;
        private Map<String, Object> data;
    
        private R(){}
    
        public static R ok() {
            R r = new R();
            r.setMessage("请求成功");
            r.setCode(200);
            return r;
        }
    
        public static R error() {
            R r = new R();
            r.setMessage("请求失败");
            r.setCode(400);
            return r;
        }
    
        public static R condition(boolean condition, Consumer<R> ok, Consumer<R> error) {
            R r = null;
            if (condition) {
                r = R.ok();
                ok.accept(r);
            } else {
                r = R.error();
                error.accept(r);
            }
            return r;
        }
    
        public R conditions(boolean condition, Consumer<R> ok, Consumer<R> error) {
            if (condition) {
                ok.accept(this);
            } else {
                error.accept(this);
            }
            return this;
        }
        
        public R data(String key, Object value) {
            if (this.data == null) {
                this.data = new HashMap<>();
            }
            data.put(key, value);
            return this;
        }
    
        public R data(Map<String, Object> data) {
            this.data = data;
            return this;
        }
    
        public R code(Integer code) {
            this.code = code;
            return this;
        }
    
        public R message(String message) {
            this.message = message;
            return this;
        }
    }
    
  5. JWTUtils

    public class JWTUtils {
        /**
         * 秘钥
         */
        private final static String secret = "%#$SKHJFas)(";
        /**
         * 分钟
         */
        private final static int time = 30;
        public static String getToken(String id) {
            return JWT.create()
                    //payload
                    .withClaim("id", id)
                    //指定过期时间
                    .withExpiresAt(new Date(System.currentTimeMillis() + time * 60 * 1000))
                    //signature
                    .sign(Algorithm.HMAC384(secret));
        }
    
        /**
         * 检验token
         */
        public static DecodedJWT verifyToken(String token) {
            return JWT.require(Algorithm.HMAC384(secret)).build().verify(token);
        }
    
        /**
         * 获取用户id
         */
        public static String getId(String token) {
            DecodedJWT decodedJWT = verifyToken(token);
            return decodedJWT.getClaim("id").asString();
        }
    }
    

# 2.2 数据库

  1. 实体类

    @Data
    @AllArgsConstructor
    @NoArgsConstructor
    @TableName("t_user")
    public class User implements Serializable {
        private static final long serialVersionUID = -40356785423868312L;
        @TableId(type = IdType.ASSIGN_ID)
        private String id;
        private String username;
        private String password;
        private String nickname;
    }
    
  2. 数据库

    DROP TABLE IF EXISTS `t_user`;
    CREATE TABLE `t_user`  (
      `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
      `username` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
      `password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
      `nickname` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
    
  3. service以及mapper

    public interface UserService extends IService<User> {
    }
    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> {
    }
    
    public interface UserMapper extends BaseMapper<User> {
    }
    
  4. yaml配置文件

    spring:
        datasource:
            driver-class-name: com.mysql.cj.jdbc.Driver
            url: jdbc:mysql://localhost:3306/spring_security?allowPublicKeyRetrieval=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai
            username: root
            password: 123456
        redis:
            host: 101.43.178.24
            port: 6379
            password: PBKXD31Zo4Tz5XVO
            database: 0
    
    mybatis-plus:
        configuration:
            log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    
  5. LoginUser用户封装请求的用户

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class LoginUser implements UserDetails {
        private User user;
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            return null;
        }
    
        @JSONField(serialize = false)
        @Override
        public String getPassword() {
            return user.getPassword();
        }
    
        @JSONField(serialize = false)
        @Override
        public String getUsername() {
            return user.getUsername();
        }
    
        @JSONField(serialize = false)
        public String getId() {
            return user.getId();
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return true;
        }
    }
    
  6. UserDetailsServiceImpl用于从数据库中获取用户数据并认证和授权

    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
        @Autowired
        private UserService userService;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            QueryWrapper<User> wrapper = new QueryWrapper<>();
            wrapper.eq("username", username);
            User user = userService.getOne(wrapper);
            if (Objects.isNull(user)) {
                throw new UsernameNotFoundException("用户名不存在");
            }
            return new LoginUser(user);
        }
    }
    
  7. 启动测试

    注:密码如果是明文存储,数据库需要存储为如下格式

    {noop}xxx
    

# 2.3 加密密码

编写一个Security的配置类即可

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    /**
     * 替换默认的密码校验
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

# 2.4 自定义登录

  1. SecurityConfig的配置

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        /**
         * 替换默认的密码校验
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        /**
         * 这个Bean用来认证
         */
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    // 关闭csrf防护
                    .csrf().disable()
                    // 不通过Session获取SecurityContext
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    // 放行的登录注册接口
                    .antMatchers("/user/login", "/user/register").anonymous()
                    // 拦截除上述的所有请求
                    .anyRequest().authenticated();
    
        }
    }
    
  2. 编写登录接口

    @RestController
    @RequestMapping("/user")
    public class UserController {
        @Autowired
        private UserService userService;
    
        @PostMapping("login")
        public R login(@RequestBody User user){
            return R.ok()
                    .data("token", userService.login(user))
                    .message("登录成功");
        }
    }
    
  3. service

    @Service
    public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
        @Autowired
        private AuthenticationManager authenticationManager;
    
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
    
        @Override
        public String login(User user) {
            // 登录认证
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                    user.getUsername(),
                    user.getPassword()
            );
            Authentication authenticate = authenticationManager.authenticate(authenticationToken);
            if (ObjectUtils.isEmpty(authenticate)) {
                throw new RuntimeException("登录失败");
            }
    
            LoginUser principal = (LoginUser) authenticate.getPrincipal();
            String id = principal.getId();
            // 认证通过,用户信息存储redis
            redisTemplate.opsForValue().set("userInfo"+id, principal);
            // 返回token
            return JWTUtils.getToken(id);
        }
    }
    

# 2.5 自定义过滤器

自定义过滤器,解析token中的userid,获取用户信息,并将其存入SecurityContextHolder,方便SpringSecurity后续的过滤链使用

  1. 编写过滤器

    @Component
    public class AuthenticationTokenFilter extends OncePerRequestFilter {
        @Autowired
        private RedisTemplate<String, Object> redisTemplate;
        @Override
        protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
            String token = request.getHeader("Authorization");
            // 没有token 放行
            if (!StringUtils.hasText(token)) {
                filterChain.doFilter(request, response);
                return;
            }
            String id = JWTUtils.getId(token);
            Object o = redisTemplate.opsForValue().get("userInfo" + id);
            if (o == null) {
                throw new RuntimeException("用户未登录");
            }
    
            LoginUser user = (LoginUser) o;
            // 这里必须使用三个参数的构造函数
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
                    user,
                    null,
                    null
            );
            // 将用户信息存入SecurityContextHolder
            SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            filterChain.doFilter(request, response);
        }
    }
    
  2. 修改config进行注册

    @Configuration
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private AuthenticationTokenFilter authenticationTokenFilter;
    
        /**
         * 替换默认的密码校验
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        /**
         * 这个Bean用来认证
         */
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    // 关闭csrf防护
                    .csrf().disable()
                    // 不通过Session获取SecurityContext
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    // 放行的登录注册接口
                    .antMatchers("/user/login").anonymous()
                    // 拦截除上述的所有请求
                    .anyRequest().authenticated();
    
            // 注册过滤器
            http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
        }
    }
    

# 2.6 登出

service删除redis中缓存的用户信息即可

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public void logout() {
        UsernamePasswordAuthenticationToken authentication = (UsernamePasswordAuthenticationToken)SecurityContextHolder.getContext().getAuthentication();
        LoginUser user = (LoginUser)authentication.getPrincipal();
        redisTemplate.delete("userInfo" + user.getId());
    }
}

# 3. 授权

权限字符串

模块名:实体:实体操作	// edu:user:delete

# 3.1 数据库设计

权限一般使用RBAC模型

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for t_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_perms`;
CREATE TABLE `t_perms`  (
  `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `perms_key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for t_role
-- ----------------------------
DROP TABLE IF EXISTS `t_role`;
CREATE TABLE `t_role`  (
  `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `name` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for t_role_perms
-- ----------------------------
DROP TABLE IF EXISTS `t_role_perms`;
CREATE TABLE `t_role_perms`  (
  `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `perms_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `role_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for t_user
-- ----------------------------
DROP TABLE IF EXISTS `t_user`;
CREATE TABLE `t_user`  (
  `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `username` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `nickname` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Table structure for t_user_role
-- ----------------------------
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role`  (
  `id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
  `user_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  `role_id` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 1;

# 3.2 授权配置

  1. 在启动类上或者配置类上开启相关配置

    @EnableGlobalMethodSecurity(prePostEnabled = true)
    
  2. 修改用户类

    @Data
    @NoArgsConstructor
    public class LoginUser implements UserDetails {
        private User user;
        private List<String> permissions;
        @JSONField(serialize = false)		// 不要序列化此字段
        private List<GrantedAuthority> authorities;
    
        public LoginUser(User user, List<String> permissions) {
            this.user = user;
            this.permissions = permissions;
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            if (authorities == null) {
                authorities = new ArrayList<>();
                permissions.forEach(item -> {
                    authorities.add(new SimpleGrantedAuthority(item));
                });
            }
            return authorities;
        }
    
        @JSONField(serialize = false)
        @Override
        public String getPassword() {
            return user.getPassword();
        }
    
        @JSONField(serialize = false)
        @Override
        public String getUsername() {
            return user.getUsername();
        }
    
        @JSONField(serialize = false)
        public String getId() {
            return user.getId();
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return true;
        }
    }
    
  3. service

    @Service
    public class UserDetailsServiceImpl implements UserDetailsService {
        @Autowired
        private UserService userService;
        @Autowired
        private UserMapper userMapper;
    
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            QueryWrapper<User> wrapper = new QueryWrapper<>();
            wrapper.eq("username", username);
            User user = userService.getOne(wrapper);
            if (Objects.isNull(user)) {
                throw new UsernameNotFoundException("用户名不存在");
            }
    
            return new LoginUser(user, userMapper.selectPermsByUserId(user.getId()));
        }
    }
    
    # 查询用户所对应的权限
    SELECT DISTINCT
    	t_perms.perms_key 
    FROM
    	t_user_role
    	LEFT JOIN t_role ON t_user_role.role_id = t_role.id
    	LEFT JOIN t_role_perms ON t_role_perms.role_id = t_role.id
    	LEFT JOIN t_perms ON t_perms.id = t_role_perms.role_id 
    WHERE
    	t_user_role.user_id = 1502174521665257474 & t_perms.perms_key IS NOT NULL
    
  4. 使用

    @RestController
    public class HelloController {
        @GetMapping("/hello")
        @PreAuthorize("hasAuthority('sys:user:update')")   // 一个权限
        public R hello() {
            return R.ok()
                    .message("hello world");
        }
    }
    

# 4. 异常处理

  1. 认证失败

    public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            response.setStatus(200);
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
    
            String result = JSON.toJSONString(R.error().message("认证失败"));
            try(PrintWriter writer = response.getWriter()) {
                writer.println(result);
            }
        }
    }
    
  2. 授权失败

    public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
            response.setStatus(200);
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");
    
            String result = JSON.toJSONString(R.error().message("权限不足"));
            try(PrintWriter writer = response.getWriter()) {
                writer.println(result);
            }
        }
    }
    
  3. 配置异常处理

    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private AuthenticationTokenFilter authenticationTokenFilter;
    
        /**
         * 替换默认的密码校验
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        /**
         * 这个Bean用来认证
         */
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    // 关闭csrf防护
                    .csrf().disable()
                    // 不通过Session获取SecurityContext
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    // 放行的登录注册接口
                    .antMatchers("/user/login", "/user/register").anonymous()
                    // 拦截除上述的所有请求
                    .anyRequest().authenticated();
    
            // 注册过滤器
            http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    
            // 配置异常处理
            http.exceptionHandling()
                    .authenticationEntryPoint(new AuthenticationEntryPointImpl())
                    .accessDeniedHandler(new AccessDeniedHandlerImpl());
        }
    }
    

# 5. 跨域问题

  1. SpringBoot的跨域

    @Configuration
    public class CorsConfig implements WebMvcConfigurer {
        @Override
        public void addCorsMappings(CorsRegistry registry) {
                    // 允许跨域的路径
            registry.addMapping("/**")
                    // 允许跨域的域名
                    .allowedOriginPatterns("*")
                    // 是否允许cookie
                    .allowCredentials(true)
                    // 允许跨域的方法
                    .allowedMethods("GET", "POST", "DELETE", "PUT")
                    // 允许跨域的请求头
                    .allowedHeaders("*")
                    // 跨域的允许时间
                    .maxAge(3600);
        }
    }
    
  2. SpringSecurity的配置

    @Configuration
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
    
        @Autowired
        private AuthenticationTokenFilter authenticationTokenFilter;
    
        /**
         * 替换默认的密码校验
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        /**
         * 这个Bean用来认证
         */
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    // 关闭csrf防护
                    .csrf().disable()
                    // 不通过Session获取SecurityContext
                    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                    .and()
                    .authorizeRequests()
                    // 放行的登录注册接口
                    .antMatchers("/user/login", "/user/register").anonymous()
                    // 拦截除上述的所有请求
                    .anyRequest().authenticated();
    
            // 注册过滤器
            http.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);
    
            // 配置异常处理
            http.exceptionHandling()
                    .authenticationEntryPoint(new AuthenticationEntryPointImpl())
                    .accessDeniedHandler(new AccessDeniedHandlerImpl());
            
            // 允许跨域
            http.cors();
        }
    }
    

# 6. 校验方法

# 6.1 Security提供

  1. @PreAuthorize("hasAuthority('sys:user:update')")   // 一个权限
    public R hello() {
        return R.ok()
            .message("hello world");
    }
    
  2. 多个权限,满足一个即可

    @PreAuthorize("hasAnyAuthority('sys:user:update', 'sys:user:select')")  // 多个权限
    public R hello() {
        return R.ok()
            .message("hello world");
    }
    
  3. 角色判断,注意⭐️数据库中存储的角色必须有ROLE_前缀

    @PreAuthorize("hasRole('admin')")
    public R hello() {
        return R.ok()
            .message("hello world");
    }
    
  4. 多角色

    @PreAuthorize("hasAnyRole('admin', 'user')")
    public R hello() {
        return R.ok()
            .message("hello world");
    }
    

# 6.2 自定义校验方法

  1. 编写校验方法

    @Component("meinilExperssionRoot")
    public class MeinilExperssionRoot {
        public boolean hasAuthority(String authority) {
            Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
            LoginUser user = (LoginUser)authentication.getPrincipal();
    
            List<String> permissions = user.getPermissions();
            return permissions.contains(authority);
        }
    }
    
  2. 使用SPEL表达式

    @PreAuthorize("@meinilExperssionRoot.hasAuthority('sys:user:update')")
    public R hello() {
        return R.ok()
            .message("hello world");
    }
    
最后修改时间: 5 minutes ago