SpringSecurity入门(四)

18、权限管理/授权

18.1、针对url配置

  • 配置SecurityConfig
java 复制代码
package com.wanqi.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;

import java.util.Collections;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2022/9/5
 * @Author wandaren
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public PasswordEncoder bcryptPasswordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public UserDetailsService inMemoryUsers(PasswordEncoder encoder) {
        // The builder will ensure the passwords are encoded before saving in memory
        UserDetails user = User.withUsername("user")
                .password(encoder.encode("123"))
                .roles("USER")
                .build();
        UserDetails admin = User.withUsername("admin")
                .password(encoder.encode("123"))
                .roles("ADMIN")
                .build();
        UserDetails qifeng = User.withUsername("qifeng")
                .password(encoder.encode("123"))
                .authorities("READ_HELLO","ROLE_ADMIN","ROLE_USER")
                .build();

        return new InMemoryUserDetailsManager(user, admin, qifeng);
    }

    @Bean
    SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        httpSecurity.authorizeRequests()
                //权限READ_HELLO
                .mvcMatchers("/hello").hasAuthority("READ_HELLO")
                //角色
                .mvcMatchers("/admin").hasRole("ADMIN")
                .mvcMatchers("/user").hasRole("USER")
                .anyRequest().authenticated()
                .and()
                .csrf().disable()
                .formLogin()
                .and()
                .logout(logout -> logout.logoutRequestMatcher(
                        //自定义注销url
                        new OrRequestMatcher(
                                new AntPathRequestMatcher("/aa", "GET")))
                        .logoutSuccessUrl("/login")
                )
                .userDetailsService(inMemoryUsers(bcryptPasswordEncoder()))
        ;

        return httpSecurity.build();
    }


}
  • 编码HelloController
java 复制代码
package com.wanqi.controller;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2022/9/6
 * @Author wandaren
 */

@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String hello(){
        return "hello";
    }

    @RequestMapping("/admin")
    public String admin(){
        return "admin";
    }

    @RequestMapping("/user")
    public String user(){
        return "user";
    }
}

18.2、针对方法配置

  • 基于注解EnableMethodSecurity

  • @EnableMethodSecurity(prePostEnabled \](/EnableMethodSecurity(prePostEnabled ) = true)

  • 配置开启注解

java 复制代码
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
  • 编码测试接口
java 复制代码
package com.wanqi.controller;

import com.wanqi.pojo.DataDamo;
import org.springframework.security.access.prepost.PostAuthorize;
import org.springframework.security.access.prepost.PostFilter;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.access.prepost.PreFilter;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

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

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2022/9/6
 * @Author wandaren
 */
@RestController
@RequestMapping("/hi")
public class MethodController {

    @PreAuthorize("hasRole('ADMIN') and authentication.name=='qifeng'")
    @RequestMapping
    public String hi() {
        return "hi";
    }

    @PreAuthorize("authentication.name==#name")
    @RequestMapping("h1")
    public String hello(String name) {
        return "hello: " + name;
    }

    //filterTarget必须是:数组/集合
    @PreFilter(value = "filterObject.id%2 != 0", filterTarget = "dataDamos")
    @RequestMapping("users")
    public String users(@RequestBody List<DataDamo> dataDamos) {
        return "hello: " + dataDamos;
    }

    @PostAuthorize("returnObject.id==1")
    @RequestMapping("userId")
    public DataDamo userId(Integer id) {
        return new DataDamo(id, "ssss");
    }

    @PostFilter("filterObject.id>5")
    @RequestMapping("lists")
    public List<DataDamo> lists() {
        List<DataDamo> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add(new DataDamo(i, "sss" + i));
        }
        return list;
    }

}

19、基于数据库权限验证

19.1、表结构

菜单/权限 角色(可访问)
/admin/** ROLE_ADMIN
/user/** ROLE_USER
/guest/** ROLE_GUEST
用户 角色
admin ADMIN、USER
user USER
qifeng GUEST

19.2、sql

  • 菜单/权限
sql 复制代码
CREATE TABLE `menu` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `pattern` varchar(128) COLLATE utf8mb4_general_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

INSERT INTO menu (pattern) VALUES
	 ('/admin/**'),
	 ('/user/**'),
	 ('/guest/**');
  • 角色
sql 复制代码
CREATE TABLE `role` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `name` varchar(100) COLLATE utf8mb4_general_ci NOT NULL,
  `name_zh` varchar(100) COLLATE utf8mb4_general_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

INSERT INTO `role` (name,name_zh) VALUES
	 ('ROLE_ADMIN','管理员'),
	 ('ROLE_USER','普通用户'),
	 ('ROLE_GUEST','游客');
  • 菜单/权限-角色关系
sql 复制代码
CREATE TABLE `menu_role` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `mid` bigint NOT NULL,
  `rid` bigint NOT NULL,
  PRIMARY KEY (`id`),
  KEY `menu_role_mid_IDX` (`mid`,`rid`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

INSERT INTO `security`.menu_role (mid,rid) VALUES
	 (1,1),
	 (2,2),
	 (3,2),
	 (3,3);
  • 用户
sql 复制代码
CREATE TABLE `user` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `username` varchar(100) COLLATE utf8mb4_general_ci NOT NULL COMMENT '用户名',
  `password` varchar(500) COLLATE utf8mb4_general_ci NOT NULL COMMENT '密码',
  `accountNonExpired` tinyint(1) NOT NULL COMMENT '账户是否过期',
  `enabled` tinyint(1) NOT NULL COMMENT '账户是否激活',
  `accountNonLocked` tinyint(1) NOT NULL COMMENT '账户是否被锁定',
  `credentialsNonExpired` tinyint(1) NOT NULL COMMENT '密码是否过期',
  PRIMARY KEY (`id`),
  KEY `user_username_IDX` (`username`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;


INSERT INTO `user` (username,password,accountNonExpired,enabled,accountNonLocked,credentialsNonExpired) VALUES
	 ('admin','{noop}123',1,1,1,1),
	 ('user','{noop}123',1,1,1,1),
	 ('qifeng','{noop}123',1,1,1,1);
  • 用户-角色关系
sql 复制代码
CREATE TABLE `user_role` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `uid` bigint NOT NULL COMMENT '用户编号',
  `rid` bigint NOT NULL COMMENT '角色编号',
  PRIMARY KEY (`id`),
  KEY `user_role_userId_IDX` (`uid`,`rid`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

INSERT INTO `security`.user_role (uid,rid) VALUES
	 (1,1),
	 (1,2),
	 (2,2),
	 (3,3);

19.3、依赖,数据库配置

xml 复制代码
<dependencies>
        <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.2.2</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.11</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </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>
yaml 复制代码
server:
  port: 8081
spring:
  datasource:
    type: com.alibaba.druid.pool.DruidDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://172.16.156.139:3306/security?allowPublicKeyRetrieval=true
    username: wq
    password: qifeng
mybatis:
  type-aliases-package: com.wanqi.pojo
  mapper-locations: classpath:mapper/*.xml

19.4、实体类与mapper

  • 菜单
java 复制代码
package com.wanqi.pojo;

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

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2022/9/7
 * @Author wandaren
 */
public class Menu {
    private Long id;
    private String pattern;
    private List<Role> roles = new ArrayList<>();

    public Long getId() {
        return id;
    }

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

    public String getPattern() {
        return pattern;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    public Menu(String pattern) {
        this.pattern = pattern;
    }

    public Menu() {
    }

    @Override
    public String toString() {
        return "Menu{" +
                "id=" + id +
                ", pattern='" + pattern + '\'' +
                ", roles=" + roles +
                '}';
    }
}
java 复制代码
package com.wanqi.mapper;

import com.wanqi.pojo.Menu;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2022/9/7
 * @Author wandaren
 */
@Mapper
public interface MenuMapper {
    public List<Menu> getAllMenu();
}
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.wanqi.mapper.MenuMapper">

    <resultMap id="MenuResultMap" type="com.wanqi.pojo.Menu">
        <id property="id" column="id"/>
        <result property="pattern" column="pattern"/>
        <collection property="roles" ofType="com.wanqi.pojo.Role">
            <id property="id" column="rid"/>
            <result property="name" column="rname"/>
            <result property="nameZh" column="rnameZh"/>
        </collection>
    </resultMap>

    <select id="getAllMenu" resultMap="MenuResultMap">
        Select m.*, r.id as rid, r.name as rname, r.name_zh as rnameZh From menu m
        Left join menu_role mr on m.`id` = mr.`mid`
        Left join role r on r.`id` = mr.`rid`
    </select>

</mapper>
  • 角色
java 复制代码
package com.wanqi.pojo;

public class Role {
    private Long id;
    private String name;
    private String nameZh;

    public Long getId() {
        return id;
    }

    public void setId(Long 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;
    }

    public Role() {
    }

    public Role(String name, String nameZh) {
        this.name = name;
        this.nameZh = nameZh;
    }

    @Override
    public String toString() {
        return "Role{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", nameZh='" + nameZh + '\'' +
                '}';
    }
}
java 复制代码
package com.wanqi.mapper;

import com.wanqi.pojo.Role;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;

@Mapper
public interface RoleMapper {
    /**
     * 根据用户编号查询角色信息
     */
    List<Role> getRoleByUserId(@Param("userId") Long userId);
}
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.wanqi.mapper.RoleMapper">

    <select id="getRoleByUserId" parameterType="Long" resultType="com.wanqi.pojo.Role">
        select r.id,
               r.name,
               r.name_zh as nameZh
        from role r,user_role ur
        where r.id = ur.rid
          and  ur.uid= #{userId}
    </select>

</mapper>
  • 用户
java 复制代码
package com.wanqi.pojo;

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

import java.util.*;

public class User implements UserDetails {
    private Long id;
    private String username;
    private String password;
    /** 账户是否过期
     * 在MySQL中,0被认为是false,非零值被认为是true
     * */
    private Boolean accountNonExpired = true;
    /** 账户是否激活 */
    private Boolean enabled = true;
    /** 账户是否被锁定 */
    private Boolean accountNonLocked = true;
    /** 密码是否过期 */
    private Boolean credentialsNonExpired = true;
    private List<Role> roles = new ArrayList<>();

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Set<SimpleGrantedAuthority> authorities = new HashSet<>();
        roles.forEach(role -> authorities.add(new SimpleGrantedAuthority(role.getName())));
        return authorities;
    }

    public Long getId() {
        return id;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    public List<Role> getRoles() {
        return roles;
    }



    @Override
    public boolean isAccountNonExpired() {
        return accountNonExpired;
    }

    @Override
    public boolean isAccountNonLocked() {
        return accountNonLocked;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return credentialsNonExpired;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }


    @Override
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    @Override
    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

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

    public void setAccountNonExpired(Boolean accountNonExpired) {
        this.accountNonExpired = accountNonExpired;
    }

    public void setEnabled(Boolean enabled) {
        this.enabled = enabled;
    }

    public void setAccountNonLocked(Boolean accountNonLocked) {
        this.accountNonLocked = accountNonLocked;
    }

    public void setCredentialsNonExpired(Boolean credentialsNonExpired) {
        this.credentialsNonExpired = credentialsNonExpired;
    }
}
java 复制代码
package com.wanqi.mapper;

import com.wanqi.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

@Mapper
public interface UserMapper {
   /**
    * 根据用户名查询用户
    */
   User loadUserByUsername(@Param("username") String username);

   /**
    * 添加用户
    * @param user
    * @return int
    */
   int save(User user);

   /**
    *
    * 更新密码
    * @param password
    * @param username
    * @return int
    */
   int updatePassword(@Param("password")String password,@Param("username") String username);
}
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.wanqi.mapper.UserMapper">
    <select id="loadUserByUsername" parameterType="String" resultType="user">
        select * from user where username=#{username}
    </select>

    <insert id="save" parameterType="user">
        INSERT INTO `user` (username, password, accountNonExpired, enabled, accountNonLocked, credentialsNonExpired)
        VALUES (#{username}, #{password}, #{accountNonExpired}, #{enabled}, #{accountNonLocked},
                #{credentialsNonExpired});
    </insert>

    <update id="updatePassword">
        UPDATE `user`
        SET password= #{password}
        WHERE username = #{username};
    </update>

</mapper>

19.5、自定义的资源(url)权限(角色)数据获取类

java 复制代码
package com.wanqi.security.filter;

import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.wanqi.mapper.MenuMapper;
import com.wanqi.pojo.Menu;
import com.wanqi.pojo.Role;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import java.util.Collection;
import java.util.List;

/**
 * @Description 自定义的资源(url)权限(角色)数据获取类
 * @Version 1.0.0
 * @Date 2022/9/7
 * @Author wandaren
 */
@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    @Autowired
    private MenuMapper menuMapper;

    @Bean
    private AntPathMatcher antPathMatcher() {
        return new AntPathMatcher();
    }

    /**
     * 获取用户请求的某个具体的资源(url)所需要的权限(角色)集合
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        //获取当前请求对象
        String requestURI = ((FilterInvocation) object).getRequest().getRequestURI();
        //查询所有的菜单
        List<Menu> allMenu = menuMapper.getAllMenu();
        System.out.println(JSONUtil.toJsonStr(allMenu));
        for (Menu menu : allMenu) {
            if (antPathMatcher().match(menu.getPattern(), requestURI)) {
                String[] roles = menu.getRoles().stream().map(Role::getName).toArray(String[]::new);
                System.out.println(JSONUtil.toJsonStr(roles));
                return SecurityConfig.createList(roles);
            }
        }
        return null;
    }


    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        return FilterInvocation.class.isAssignableFrom(clazz);
    }
}

19.6、自定义获取账号信息,与密码自动更新

java 复制代码
package com.wanqi.service.impl;

import cn.hutool.json.JSONUtil;
import com.wanqi.mapper.RoleMapper;
import com.wanqi.mapper.UserMapper;
import com.wanqi.pojo.Role;
import com.wanqi.pojo.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsPasswordService;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;

import java.util.List;

@Component("userDetailsImpl")
public class UserDetailsImpl implements UserDetailsService, UserDetailsPasswordService {
    @Autowired
   private UserMapper userMapper;
    @Autowired
   private RoleMapper roleMapper;

    @Override
    public org.springframework.security.core.userdetails.UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.loadUserByUsername(username);
        System.out.println("---"+JSONUtil.toJsonStr(user));

        if(user == null){
            throw new UsernameNotFoundException("用户名不存在");
        }
        List<Role> roles = roleMapper.getRoleByUserId(user.getId());
        System.out.println("---"+JSONUtil.toJsonStr(roles));
        user.setRoles(roles);
        return user;
    }

    @Override
    public UserDetails updatePassword(UserDetails user, String newPassword) {
        int state = userMapper.updatePassword(newPassword, user.getUsername());
        if (state == 1) {
            ((User) user).setPassword(newPassword);
        }
        return user;
    }
}

19.7、自定义RememberMeServices实现类

java 复制代码
package com.wanqi.service;

import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.rememberme.AbstractRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenBasedRememberMeServices;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;

import javax.servlet.http.HttpServletRequest;

/**
 * @Description 自定义RememberMeServices实现类
 * @Version 1.0.0
 * @Date 2022/9/5
 * @Author wandaren
 */

public class RememberMeServices extends PersistentTokenBasedRememberMeServices {

    public RememberMeServices(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) {
        super(key, userDetailsService, tokenRepository);
    }

    @Override
    protected boolean rememberMeRequested(HttpServletRequest request, String parameter) {
        Object attribute = request.getAttribute(AbstractRememberMeServices.DEFAULT_PARAMETER);
        if (attribute == null) {
            return false;
        }
        String paramValue = attribute.toString();
        return paramValue.equalsIgnoreCase("true") || paramValue.equalsIgnoreCase("on")
                || paramValue.equalsIgnoreCase("yes") || paramValue.equals("1");
    }
}

19.8、Security配置类

java 复制代码
package com.wanqi.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wanqi.security.filter.CustomFilterInvocationSecurityMetadataSource;
import com.wanqi.service.RememberMeServices;
import com.wanqi.service.impl.UserDetailsImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
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.configurers.UrlAuthorizationConfigurer;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2022/9/5
 * @Author wandaren
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    DataSource dataSource;
    @Autowired
    CustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;

    UserDetailsImpl userDetailsImpl;

    @Autowired
    public void setUserDetailsImpl(UserDetailsImpl userDetailsImpl) {
        this.userDetailsImpl = userDetailsImpl;
    }

    @Bean
    public PasswordEncoder bcryptPasswordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Autowired
    public AuthenticationConfiguration authenticationConfiguration;

    /**
     * 获取AuthenticationManager(认证管理器),登录时认证使用
     *
     * @return
     * @throws Exception
     */
    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return authenticationConfiguration.getAuthenticationManager();
    }


    @Bean
    public PersistentTokenRepository jdbcTokenRepository(){
        JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
        jdbcTokenRepository.setDataSource(dataSource);
        //自动创建表结构,首次启动设置为true
//        jdbcTokenRepository.setCreateTableOnStartup(true);
        return jdbcTokenRepository;
    }

    @Bean
    public RememberMeServices rememberMeServices(){
        return new RememberMeServices(UUID.randomUUID().toString(), userDetailsImpl, jdbcTokenRepository());
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
        //获取工厂对象
        ApplicationContext applicationContext = httpSecurity.getSharedObject(ApplicationContext.class);
        //设置自定义url权限处理
        httpSecurity.apply(new UrlAuthorizationConfigurer<>(applicationContext))
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
                        //是否拒绝公共资源访问
                        object.setRejectPublicInvocations(false);
                        return object;
                    }
                });
        httpSecurity.formLogin()
                .and()
                .logout(logout -> {
                    logout
                            .logoutRequestMatcher(
                                    //自定义注销url
                                    new OrRequestMatcher(
                                            new AntPathRequestMatcher("/aa", "GET"),
                                            new AntPathRequestMatcher("/bb", "POST")))
                            //前后端分离时代自定义注销登录处理器
                            .logoutSuccessHandler(new LogoutSuccessHandler() {
                                @Override
                                public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                                    Map<String, Object> map = new HashMap<>();
                                    map.put("msg", "注销成功");
                                    map.put("code", HttpStatus.OK.value());
                                    map.put("authentication", authentication);
                                    String s = new ObjectMapper().writeValueAsString(map);
                                    response.setContentType("application/json;charset=UTF-8");
                                    response.getWriter().write(s);
                                }
                            })
                            //销毁session,默认为true
                            .invalidateHttpSession(true)
                            //清除认证信息,默认为true
                            .clearAuthentication(true);
                })
                //指定UserDetailsService来切换认证信息不同的存储方式(数据源)
                .userDetailsService(userDetailsImpl)
                .rememberMe()
                .rememberMeServices(rememberMeServices())
                .tokenRepository(jdbcTokenRepository())
                .and()
                //禁止csrf跨站请求保护
                .csrf().disable();

        return httpSecurity.build();
    }

}

20、OAuth2

20.1、基于gitee实现快速登陆

  • 依赖
xml 复制代码
 <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-oauth2-client</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.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>
  • yaml配置
yaml 复制代码
spring:
  security:
    oauth2:
      client:
        registration:
          gitee:
            client-id: daf0946aa26c28a661bbfb5bdb89357f8b90e121b53d98ba8b383afd348904e0
            client-secret: 1021637c412d22bd2b706f15c0c5c9dad6df859d9f4a01e36b93575b50d98c5c
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:8080/login/oauth2/code/gitee
            client-name: gitee
        provider:
          gitee:
            authorization-uri: https://gitee.com/oauth/authorize
            token-uri: https://gitee.com/oauth/token
            user-info-uri: https://gitee.com/api/v5/user
            user-name-attribute: name
  • security配置类
java 复制代码
package com.wanqi.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.web.SecurityFilterChain;


/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2022/9/8
 * @Author wandaren
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                .authorizeHttpRequests(authorize -> authorize
                        .anyRequest().authenticated()
                )
                .oauth2Login();
        return http.build();
    }

    @Bean
    public ClientRegistrationRepository clientRegistrationRepository() {
        return new InMemoryClientRegistrationRepository(this.giteeClientRegistration());
    }

    private ClientRegistration giteeClientRegistration() {
        return ClientRegistration.withRegistrationId("gitee")
                .clientId("daf0946aa26c28a661bbfb5bdb89357f8b90e121b53d98ba8b383afd348904e0")
                .clientSecret("1021637c412d22bd2b706f15c0c5c9dad6df859d9f4a01e36b93575b50d98c5c")
                .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
                .redirectUri("http://localhost:8080/login/oauth2/code/gitee")
                .authorizationUri("https://gitee.com/oauth/authorize")
                .tokenUri("https://gitee.com/oauth/token")
                .userInfoUri("https://gitee.com/api/v5/user")
                .userNameAttributeName("name")
                .clientName("gitee")
                .build();
    }
}
latex 复制代码
 (1)client_id、client-secret替换为Gitee获取的数据
 (2)authorization-grant-type:授权模式使用授权码模式
 (3)redirect-uri:回调地址,填写的与Gitee上申请的一致
 (4)client-name:客户端名称,可以在登录选择页面上显示
  Gitee的OAuth登录需要自定义provider,Spring Security OAuth提供了配置的方式来实现。
 (5)authorization-uri:授权服务器地址
 (6)token-uri:授权服务器获取token地址
 (7)user-info-uri:授权服务器获取用户信息的地址
 (8)user-name-attribute:用户信息中的用户名属性
  • gitee创建第三方应用

20.2、基于内存搭建授权服务器

  • 引入依赖,版本使用2.2.5.RELEASE
xml 复制代码
 <dependencies>
        <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.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.2.5.RELEASE</version>
        </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>
  • 配置security
java 复制代码
package com.wanqi.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
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.core.Authentication;
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.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2022/9/8
 * @Author wandaren
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean(value = "bcryptPasswordEncoder")
    public PasswordEncoder bcryptPasswordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Bean
    public UserDetailsService inMemoryUsers(@Qualifier("bcryptPasswordEncoder") PasswordEncoder encoder) {
        UserDetails admin = User.withUsername("admin")
                .password(encoder.encode("123"))
                .roles("USER", "ADMIN")
                .build();
        return new InMemoryUserDetailsManager(admin);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //开启权限验证
        http.authorizeRequests()
                //permitAll直接放行,必须在anyRequest().authenticated()前面
                .mvcMatchers("/toLogin").permitAll()
                .mvcMatchers("/index").permitAll()
                //anyRequest所有请求都需要认证
                .anyRequest().authenticated()
                .and()
                //使用form表单验证
                .formLogin().and()
                //注销
                .logout(logout -> {
                    logout
                            //指定默认注销url,默认请求方式GET
                            //.logoutUrl("/logout")
                            .logoutRequestMatcher(
                                    //自定义注销url
                                    new OrRequestMatcher(
                                            new AntPathRequestMatcher("/aa", "GET")))
                            //注销成功后跳转页面
                            //.logoutSuccessUrl("/toLogin")
                            //前后端分离时代自定义注销登录处理器
                            .logoutSuccessHandler(new LogoutSuccessHandler() {
                                @Override
                                public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                                    Map<String, Object> map = new HashMap<>();
                                    map.put("msg", "注销成功");
                                    map.put("code", HttpStatus.OK.value());
                                    map.put("authentication", authentication);
                                    String s = new ObjectMapper().writeValueAsString(map);
                                    response.setContentType("application/json;charset=UTF-8");
                                    response.getWriter().write(s);
                                }
                            })
                            //销毁session,默认为true
                            .invalidateHttpSession(true)
                            //清除认证信息,默认为true
                            .clearAuthentication(true);
                })
                //指定UserDetailsService来切换认证信息不同的存储方式(数据源)
                .userDetailsService(inMemoryUsers(bcryptPasswordEncoder()))
                //禁止csrf跨站请求保护
                .csrf().disable();
    }

}
  • 自定义 授权服务器配置
java 复制代码
package com.wanqi.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;

import javax.annotation.Resource;

/**
 * @Description 自定义 授权服务器配置
 * @Version 1.0.0
 * @Date 2022/9/8
 * @Author wandaren
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Resource
    private PasswordEncoder bcryptPasswordEncoder;

    /**
     * 用来配置授权服务器可以为那些客户端授权
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("app")
                //注册客户端密钥
                .secret(bcryptPasswordEncoder.encode("secret"))
                .redirectUris("https://cn.bing.com")
                //授权码模式,5选一
                .authorizedGrantTypes("authorization_code")
                //.authorizedGrantTypes("client_credentials", "password", "implicit", "authorization_code", "refresh_token");
                //令牌容许获取的资源权限
                .scopes("read:user")
        ;
    }
}
latex 复制代码
请求是否同意授权:http://127.0.0.1:8080/oauth/authorize?client_id=app&redirect_uri=https://cn.bing.com&response_type=code
获取令牌:http://app:secret@localhost:8080/oauth/token
  • 获取令牌
  • 刷新令牌
  • 修改自定义 授权服务器配置
latex 复制代码
.authorizedGrantTypes("authorization_code","refresh_token")

 @Override
publicvoidconfigure(AuthorizationServerEndpointsConfigurerendpoints)throwsException{
endpoints.userDetailsService(userDetailsService);
}
java 复制代码
package com.wanqi.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;

import javax.annotation.Resource;

/**
 * @Description 自定义 授权服务器配置
 * @Version 1.0.0
 * @Date 2022/9/8
 * @Author wandaren
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Resource
    private PasswordEncoder bcryptPasswordEncoder;
    @Resource
    private UserDetailsService userDetailsService;

    /**
     * 用来配置授权服务器可以为那些客户端授权
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("app")
                //注册客户端密钥
                .secret(bcryptPasswordEncoder.encode("secret"))
                .redirectUris("https://cn.bing.com")
                /* 授权码模式:client_credentials
                *  刷新令牌:refresh_token
                *  */
                .authorizedGrantTypes("authorization_code","refresh_token")
                //.authorizedGrantTypes("client_credentials", "password", "implicit", "authorization_code", "refresh_token");
                //令牌容许获取的资源权限
                .scopes("read:user")
        ;
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.userDetailsService(userDetailsService);
    }
}

20.3、基于redis搭建授权服务器

1、引入依赖,spirng-boot版本2.2.5.RELEASE

xml 复制代码
 <dependencies>
        <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.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </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>

2、配置redis

yaml 复制代码
spring:
  redis:
    port: 6379
    host: 172.16.156.139
    password: qifeng
    database: 1 #指定数据库

3、Security配置类

java 复制代码
package com.wanqi.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.core.Authentication;
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.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2022/9/8
 * @Author wandaren
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean(value = "bcryptPasswordEncoder")
    public PasswordEncoder bcryptPasswordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(inMemoryUsers(bcryptPasswordEncoder()));
    }

    @Override
    @Bean
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Bean
    public UserDetailsService inMemoryUsers(@Qualifier("bcryptPasswordEncoder") PasswordEncoder encoder) {
        UserDetails admin = User.withUsername("admin")
                .password(encoder.encode("123"))
                .roles("USER", "ADMIN")
                .build();
        return new InMemoryUserDetailsManager(admin);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //开启权限验证
        http.authorizeRequests()
                //permitAll直接放行,必须在anyRequest().authenticated()前面
                .mvcMatchers("/toLogin").permitAll()
                .mvcMatchers("/index").permitAll()
                //anyRequest所有请求都需要认证
                .anyRequest().authenticated()
                .and()
                //使用form表单验证
                .formLogin().and()
                //注销
                .logout(logout -> {
                    logout
                            //指定默认注销url,默认请求方式GET
                            //.logoutUrl("/logout")
                            .logoutRequestMatcher(
                                    //自定义注销url
                                    new OrRequestMatcher(
                                            new AntPathRequestMatcher("/aa", "GET")))
                            //注销成功后跳转页面
                            //.logoutSuccessUrl("/toLogin")
                            //前后端分离时代自定义注销登录处理器
                            .logoutSuccessHandler(new LogoutSuccessHandler() {
                                @Override
                                public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                                    Map<String, Object> map = new HashMap<>();
                                    map.put("msg", "注销成功");
                                    map.put("code", HttpStatus.OK.value());
                                    map.put("authentication", authentication);
                                    String s = new ObjectMapper().writeValueAsString(map);
                                    response.setContentType("application/json;charset=UTF-8");
                                    response.getWriter().write(s);
                                }
                            })
                            //销毁session,默认为true
                            .invalidateHttpSession(true)
                            //清除认证信息,默认为true
                            .clearAuthentication(true);
                })
                //指定UserDetailsService来切换认证信息不同的存储方式(数据源)
                .userDetailsService(inMemoryUsers(bcryptPasswordEncoder()))
                //禁止csrf跨站请求保护
                .csrf().disable();
    }

}

4、自定义 授权服务器配置

java 复制代码
package com.wanqi.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.annotation.Resource;

/**
 * @Description 自定义 授权服务器配置
 * @Version 1.0.0
 * @Date 2022/9/8
 * @Author wandaren
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Resource
    private PasswordEncoder bcryptPasswordEncoder;
    @Resource
    private UserDetailsService userDetailsService;
    @Autowired
    private AuthenticationManager authenticationManager ;
    @Autowired
    private RedisConnectionFactory redisConnectionFactory ;
    /**
     * 用来配置授权服务器可以为那些客户端授权
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("app")
                //注册客户端密钥
                .secret(bcryptPasswordEncoder.encode("secret"))
                .redirectUris("https://cn.bing.com")
                /* 授权码模式:client_credentials
                 * 简化模式:implicit
                 * 密码模式:password
                 * 客户端模式:client_credentials
                 *  刷新令牌:refresh_token
                 *  */
                .authorizedGrantTypes("authorization_code", "refresh_token")
                //.authorizedGrantTypes("client_credentials", "password", "implicit", "authorization_code", "refresh_token");
                //令牌容许获取的资源权限
                .scopes("read:user")
                // token的有效期
                .accessTokenValiditySeconds(24*3600)
                // refresh_token的有效期
                .refreshTokenValiditySeconds(24*7*3600);
        super.configure(clients);
    }

    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.userDetailsService(userDetailsService)
                .authenticationManager(authenticationManager)
                .tokenStore(redisTokenStore());
    }

    public TokenStore redisTokenStore(){
        return new RedisTokenStore(redisConnectionFactory) ;
    }

    /*
     * 请求是否同意授权:http://127.0.0.1:8080/oauth/authorize?client_id=app&redirect_uri=https://cn.bing.com&response_type=code
     * 获取令牌:http://app:secret@localhost:8080/oauth/token
     *
     */
}

20.4、基于redis搭建资源服务器

1、导入依赖

xml 复制代码
 <dependencies>
        <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.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-resource-server</artifactId>
        </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>

2、redis配置

yaml 复制代码
spring:
  redis:
    port: 6379
    host: 172.16.156.139
    password: qifeng
    database: 1 #指定数据库
server:
  port: 8081

3、自定义资源服务器配置

java 复制代码
package com.wanqi.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

/**
 * @Description 自定义资源服务器配置
 * @Version 1.0.0
 * @Date 2022/9/8
 * @Author wandaren
 */
@Configuration
@EnableResourceServer
public class ResourceServerConfigurer extends ResourceServerConfigurerAdapter {

    @Autowired
    private RedisConnectionFactory redisConnectionFactory ;


    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        resources.tokenStore(redisTokenStore());
        super.configure(resources);
    }

    public TokenStore redisTokenStore(){
        return new RedisTokenStore(redisConnectionFactory) ;
    }

}

4、模拟资源

java 复制代码
package com.wanqi.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2022/9/8
 * @Author wandaren
 */
@RestController
public class HelloController {

    @RequestMapping("/hello")
    public String hello(){
        return "hello";
    }
}

20.5、基于jwt搭建授权服务器

1、依赖导入,版本2.2.5.RELEASE

xml 复制代码
 <dependencies>
        <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.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.2.5.RELEASE</version>
        </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>

2、Security配置

java 复制代码
package com.wanqi.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.core.Authentication;
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.crypto.factory.PasswordEncoderFactories;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.OrRequestMatcher;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @Description TODO
 * @Version 1.0.0
 * @Date 2022/9/8
 * @Author wandaren
 */
@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean(value = "bcryptPasswordEncoder")
    public PasswordEncoder bcryptPasswordEncoder() {
        return PasswordEncoderFactories.createDelegatingPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(inMemoryUsers(bcryptPasswordEncoder()));
    }

    @Override
    @Bean
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Bean
    public UserDetailsService inMemoryUsers(@Qualifier("bcryptPasswordEncoder") PasswordEncoder encoder) {
        UserDetails admin = User.withUsername("admin")
                .password(encoder.encode("123"))
                .roles("USER", "ADMIN")
                .build();
        return new InMemoryUserDetailsManager(admin);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //开启权限验证
        http.authorizeRequests()
                //permitAll直接放行,必须在anyRequest().authenticated()前面
                //anyRequest所有请求都需要认证
                .anyRequest().authenticated()
                .and()
                //使用form表单验证
                .formLogin().and()
                //注销
                .logout(logout -> {
                    logout
                            //指定默认注销url,默认请求方式GET
                            //.logoutUrl("/logout")
                            .logoutRequestMatcher(
                                    //自定义注销url
                                    new OrRequestMatcher(
                                            new AntPathRequestMatcher("/aa", "GET")))
                            //注销成功后跳转页面
                            //.logoutSuccessUrl("/toLogin")
                            //前后端分离时代自定义注销登录处理器
                            .logoutSuccessHandler(new LogoutSuccessHandler() {
                                @Override
                                public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
                                    Map<String, Object> map = new HashMap<>();
                                    map.put("msg", "注销成功");
                                    map.put("code", HttpStatus.OK.value());
                                    map.put("authentication", authentication);
                                    String s = new ObjectMapper().writeValueAsString(map);
                                    response.setContentType("application/json;charset=UTF-8");
                                    response.getWriter().write(s);
                                }
                            })
                            //销毁session,默认为true
                            .invalidateHttpSession(true)
                            //清除认证信息,默认为true
                            .clearAuthentication(true);
                })
                //指定UserDetailsService来切换认证信息不同的存储方式(数据源)
                .userDetailsService(inMemoryUsers(bcryptPasswordEncoder()))
                //禁止csrf跨站请求保护
                .csrf().disable();
    }

}

3、jwt内容增强

java 复制代码
package com.wanqi.config;

import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import sun.jvm.hotspot.opto.HaltNode;

import java.util.HashMap;
import java.util.Map;

/**
 * @Description jwt内容增强
 * @Version 1.0.0
 * @Date 2022/9/9
 * @Author wandaren
 */
public class JwtTokenEnhancer implements TokenEnhancer {
    @Override
    public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
        Map<String,Object> map = new HashMap<>();
        map.put("test", "jwt内容增强");
        ((DefaultOAuth2AccessToken)accessToken).setAdditionalInformation(map);
        return accessToken;
    }
}

4、授权服务器配置

java 复制代码
package com.wanqi.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenEnhancerChain;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

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

@Configuration
// 开启授权服务器的功能
@EnableAuthorizationServer
public class JWTAuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private UserDetailsService userDetailsService;


    /**
     * 添加第三方的客户端
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                // 第三方客户端的名称
                .withClient("app")
                //  第三方客户端的密钥
                .secret(passwordEncoder.encode("secret"))
                .redirectUris("https://cn.bing.com")
                /* 授权码模式:client_credentials
                 * 简化模式:implicit
                 * 密码模式:password
                 * 客户端模式:client_credentials
                 *  刷新令牌:refresh_token
                 *  */
                .authorizedGrantTypes("authorization_code", "refresh_token")
                //第三方客户端的授权范围
                .scopes("all")
                // token的有效期
                .accessTokenValiditySeconds(24 * 3600)
                // refresh_token的有效期
                .refreshTokenValiditySeconds(24 * 7 * 3600);
        super.configure(clients);
    }

    /**
     * 配置验证管理器,UserdetailService
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        super.configure(endpoints);
        //配置jwt增强内容
        TokenEnhancerChain tokenEnhancerChain = new TokenEnhancerChain();
        List<TokenEnhancer> list = new ArrayList<>();
        list.add(jwtTokenEnhancer());
        list.add(jwtAccessTokenConverter());
        tokenEnhancerChain.setTokenEnhancers(list);
        endpoints.authenticationManager(authenticationManager)
                .userDetailsService(userDetailsService)
                //设置token 存储策略
                .tokenStore(jwtTokenStore())
                .accessTokenConverter(jwtAccessTokenConverter())
                .tokenEnhancer(tokenEnhancerChain);
    }

    /**
     * jwtTokenStore
     *
     * @return
     */
    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
        tokenConverter.setSigningKey("name");
        return tokenConverter;
    }

    @Bean
    JwtTokenEnhancer jwtTokenEnhancer() {
        return new JwtTokenEnhancer();
    }
}

20.6、基于jwt搭建资源服务器

1、依赖导入,版本2.2.5.RELEASE

xml 复制代码
<dependencies>
        <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.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
            <version>2.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-oauth2-resource-server</artifactId>
        </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>

2、资源服务器配置

java 复制代码
package com.wanqi.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.JwtAccessTokenConverter;
import org.springframework.security.oauth2.provider.token.store.JwtTokenStore;

/**
 * @Description 自定义资源服务器配置
 * @Version 1.0.0
 * @Date 2022/9/8
 * @Author wandaren
 */
@Configuration
@EnableResourceServer
public class JWTResourceServerConfigurer extends ResourceServerConfigurerAdapter {

    @Override
    public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
        super.configure(resources);
        resources.resourceId("app")
                .tokenStore(jwtTokenStore())
                .stateless(true);
    }

    @Bean
    public TokenStore jwtTokenStore() {
        return new JwtTokenStore(jwtAccessTokenConverter());
    }

    @Bean
    public JwtAccessTokenConverter jwtAccessTokenConverter() {
        JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();
        tokenConverter.setSigningKey("name");
        return tokenConverter;
    }

}

相关文章

SpringSecurity入门(一)
SpringSecurity入门(二)
SpringSecurity入门(三)

完结撒花

相关推荐
程序员爱钓鱼28 分钟前
Go语言实战案例-项目实战篇:新闻聚合工具
后端·google·go
IT_陈寒30 分钟前
Python开发者必须掌握的12个高效数据处理技巧,用过都说香!
前端·人工智能·后端
一只叫煤球的猫9 小时前
写代码很6,面试秒变菜鸟?不卖课,面试官视角走心探讨
前端·后端·面试
bobz9659 小时前
tcp/ip 中的多路复用
后端
bobz9659 小时前
tls ingress 简单记录
后端
皮皮林55110 小时前
IDEA 源码阅读利器,你居然还不会?
java·intellij idea
你的人类朋友11 小时前
什么是OpenSSL
后端·安全·程序员
bobz96511 小时前
mcp 直接操作浏览器
后端
前端小张同学13 小时前
服务器部署 gitlab 占用空间太大怎么办,优化思路。
后端
databook13 小时前
Manim实现闪光轨迹特效
后端·python·动效