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)
- 配置开启注解
@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 |
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创建第三方应用
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
- 获取令牌
- 刷新令牌
- 修改自定义 授权服务器配置
.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";
}
}
- http://127.0.0.1:8081/hello?access_token=cfa1e9c4-9501-466a-9b87-9ba415bd0821
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;
}
}
- 解析jwt令牌https://jwt.io/
- 请求资源服务器
- http://127.0.0.1:8081/hello?access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2NjI3NzU0MjMsInVzZXJfbmFtZSI6ImFkbWluIiwiYXV0aG9yaXRpZXMiOlsiUk9MRV9BRE1JTiIsIlJPTEVfVVNFUiJdLCJqdGkiOiI2MDhmYWIwOS1jN2NkLTQyOWItYjVhMy0yZmM3YzI1NDU3ZmQiLCJjbGllbnRfaWQiOiJhcHAiLCJzY29wZSI6WyJhbGwiXX0.NpwTkYDmtrNZRNFG5WIvxZqH9FBXhPHSojcCi7GiKfA
- 请求头使用Authorization:Bearer XXXXXX或者使用参数access_token=XXXXXXX
相关文章
SpringSecurity入门(一)
SpringSecurity入门(二)
SpringSecurity入门(三)
完结撒花