SpringSecurity入门(四)

18、权限管理/授权

18.1、针对url配置

  • 配置SecurityConfig
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
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)

image.png

  • 配置开启注解
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true)
public class SecurityConfig {
  • 编码测试接口
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

image.png

19.2、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/**');
  • 角色
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','游客');
  • 菜单/权限-角色关系
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);
  • 用户
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);
  • 用户-角色关系
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、依赖,数据库配置

<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>
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

  • 菜单
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 +
                '}';
    }
}
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 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>
  • 角色
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 + '\'' +
                '}';
    }
}
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 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>
  • 用户
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;
    }
}
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 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)权限(角色)数据获取类

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、自定义获取账号信息,与密码自动更新

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实现类

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配置类

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实现快速登陆

  • 依赖
 <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配置
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配置类
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();
    }
}
 (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创建第三方应用

image.png

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

  • 引入依赖,版本使用2.2.5.RELEASE
 <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
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();
    }

}
  • 自定义 授权服务器配置
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")
        ;
    }
}
请求是否同意授权: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
  • 获取令牌

image.png

  • 刷新令牌

image.png

  • 修改自定义 授权服务器配置
.authorizedGrantTypes("authorization_code","refresh_token")

 @Override
publicvoidconfigure(AuthorizationServerEndpointsConfigurerendpoints)throwsException{
endpoints.userDetailsService(userDetailsService);
}
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

 <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

spring:
  redis:
    port: 6379
    host: 172.16.156.139
    password: qifeng
    database: 1 #指定数据库

3、Security配置类

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、自定义 授权服务器配置

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、导入依赖

 <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配置

spring:
  redis:
    port: 6379
    host: 172.16.156.139
    password: qifeng
    database: 1 #指定数据库
server:
  port: 8081

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

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、模拟资源

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";
    }
}

image.png

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

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

 <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配置

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内容增强

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、授权服务器配置

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

<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、资源服务器配置

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;
    }

}

image.png

image.png

image.png

相关文章

SpringSecurity入门(一)

SpringSecurity入门(二)

SpringSecurity入门(三)

完结撒花

相关推荐

  1. SpringSecurity入门demo()授权

    2024-06-11 17:36:02       37 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-06-11 17:36:02       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-06-11 17:36:02       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-06-11 17:36:02       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-06-11 17:36:02       18 阅读

热门阅读

  1. 登录CarSim显示CVI版本过低,软件打不开

    2024-06-11 17:36:02       8 阅读
  2. 王冲解读:商业模式如何创新重塑行业生态

    2024-06-11 17:36:02       11 阅读
  3. 重介降浊一体化设备处理煤矿等重浊度污水

    2024-06-11 17:36:02       8 阅读
  4. 使用 Docker Compose 编排 Django 应用

    2024-06-11 17:36:02       7 阅读
  5. Rhino Linux 2024.1

    2024-06-11 17:36:02       9 阅读
  6. 第一章 - 第4节-计算机软件系统 - 课件

    2024-06-11 17:36:02       7 阅读
  7. 37、Flink 的 WindowAssigner之会话窗口示例

    2024-06-11 17:36:02       7 阅读
  8. C语言Socket实现Http的post请求

    2024-06-11 17:36:02       10 阅读