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 {@Beanpublic PasswordEncoder bcryptPasswordEncoder() {return PasswordEncoderFactories.createDelegatingPasswordEncoder();}@Beanpublic UserDetailsService inMemoryUsers(PasswordEncoder encoder) {// The builder will ensure the passwords are encoded before saving in memoryUserDetails 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);}@BeanSecurityFilterChain 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(//自定义注销urlnew 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'")@RequestMappingpublic 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.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://172.16.156.139:3306/security?allowPublicKeyRetrieval=trueusername: wqpassword: qifeng
mybatis:type-aliases-package: com.wanqi.pojomapper-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() {}@Overridepublic 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 mapperPUBLIC "-//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 mLeft 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;}@Overridepublic 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 mapperPUBLIC "-//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 nameZhfrom role r,user_role urwhere r.id = ur.ridand 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;}@Overridepublic 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;}@Overridepublic boolean isAccountNonExpired() {return accountNonExpired;}@Overridepublic boolean isAccountNonLocked() {return accountNonLocked;}@Overridepublic boolean isCredentialsNonExpired() {return credentialsNonExpired;}@Overridepublic boolean isEnabled() {return enabled;}@Overridepublic String getUsername() {return username;}public void setUsername(String username) {this.username = username;}@Overridepublic 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 mapperPUBLIC "-//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 {@Autowiredprivate MenuMapper menuMapper;@Beanprivate AntPathMatcher antPathMatcher() {return new AntPathMatcher();}/*** 获取用户请求的某个具体的资源(url)所需要的权限(角色)集合*/@Overridepublic 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;}@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {return null;}@Overridepublic 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 {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RoleMapper roleMapper;@Overridepublic 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;}@Overridepublic 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);}@Overrideprotected 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 {@AutowiredDataSource dataSource;@AutowiredCustomFilterInvocationSecurityMetadataSource customFilterInvocationSecurityMetadataSource;UserDetailsImpl userDetailsImpl;@Autowiredpublic void setUserDetailsImpl(UserDetailsImpl userDetailsImpl) {this.userDetailsImpl = userDetailsImpl;}@Beanpublic PasswordEncoder bcryptPasswordEncoder() {return PasswordEncoderFactories.createDelegatingPasswordEncoder();}@Autowiredpublic AuthenticationConfiguration authenticationConfiguration;/*** 获取AuthenticationManager(认证管理器),登录时认证使用** @return* @throws Exception*/@Beanpublic AuthenticationManager authenticationManager() throws Exception {return authenticationConfiguration.getAuthenticationManager();}@Beanpublic PersistentTokenRepository jdbcTokenRepository(){JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();jdbcTokenRepository.setDataSource(dataSource);//自动创建表结构,首次启动设置为true
// jdbcTokenRepository.setCreateTableOnStartup(true);return jdbcTokenRepository;}@Beanpublic RememberMeServices rememberMeServices(){return new RememberMeServices(UUID.randomUUID().toString(), userDetailsImpl, jdbcTokenRepository());}@Beanpublic SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {//获取工厂对象ApplicationContext applicationContext = httpSecurity.getSharedObject(ApplicationContext.class);//设置自定义url权限处理httpSecurity.apply(new UrlAuthorizationConfigurer<>(applicationContext)).withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O object) {object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);//是否拒绝公共资源访问object.setRejectPublicInvocations(false);return object;}});httpSecurity.formLogin().and().logout(logout -> {logout.logoutRequestMatcher(//自定义注销urlnew OrRequestMatcher(new AntPathRequestMatcher("/aa", "GET"),new AntPathRequestMatcher("/bb", "POST")))//前后端分离时代自定义注销登录处理器.logoutSuccessHandler(new LogoutSuccessHandler() {@Overridepublic 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: daf0946aa26c28a661bbfb5bdb89357f8b90e121b53d98ba8b383afd348904e0client-secret: 1021637c412d22bd2b706f15c0c5c9dad6df859d9f4a01e36b93575b50d98c5cauthorization-grant-type: authorization_coderedirect-uri: http://localhost:8080/login/oauth2/code/giteeclient-name: giteeprovider:gitee:authorization-uri: https://gitee.com/oauth/authorizetoken-uri: https://gitee.com/oauth/tokenuser-info-uri: https://gitee.com/api/v5/useruser-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 {@Beanpublic SecurityFilterChain filterChain(HttpSecurity http) throws Exception {http.authorizeHttpRequests(authorize -> authorize.anyRequest().authenticated()).oauth2Login();return http.build();}@Beanpublic 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();}@Beanpublic UserDetailsService inMemoryUsers(@Qualifier("bcryptPasswordEncoder") PasswordEncoder encoder) {UserDetails admin = User.withUsername("admin").password(encoder.encode("123")).roles("USER", "ADMIN").build();return new InMemoryUserDetailsManager(admin);}@Overrideprotected 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(//自定义注销urlnew OrRequestMatcher(new AntPathRequestMatcher("/aa", "GET")))//注销成功后跳转页面//.logoutSuccessUrl("/toLogin")//前后端分离时代自定义注销登录处理器.logoutSuccessHandler(new LogoutSuccessHandler() {@Overridepublic 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 {@Resourceprivate PasswordEncoder bcryptPasswordEncoder;/*** 用来配置授权服务器可以为那些客户端授权* @param clients* @throws Exception*/@Overridepublic 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 {@Resourceprivate PasswordEncoder bcryptPasswordEncoder;@Resourceprivate UserDetailsService userDetailsService;/*** 用来配置授权服务器可以为那些客户端授权* @param clients* @throws Exception*/@Overridepublic 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");}@Overridepublic 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: 6379host: 172.16.156.139password: qifengdatabase: 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();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(inMemoryUsers(bcryptPasswordEncoder()));}@Override@Beanprotected AuthenticationManager authenticationManager() throws Exception {return super.authenticationManager();}@Beanpublic UserDetailsService inMemoryUsers(@Qualifier("bcryptPasswordEncoder") PasswordEncoder encoder) {UserDetails admin = User.withUsername("admin").password(encoder.encode("123")).roles("USER", "ADMIN").build();return new InMemoryUserDetailsManager(admin);}@Overrideprotected 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(//自定义注销urlnew OrRequestMatcher(new AntPathRequestMatcher("/aa", "GET")))//注销成功后跳转页面//.logoutSuccessUrl("/toLogin")//前后端分离时代自定义注销登录处理器.logoutSuccessHandler(new LogoutSuccessHandler() {@Overridepublic 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 {@Resourceprivate PasswordEncoder bcryptPasswordEncoder;@Resourceprivate UserDetailsService userDetailsService;@Autowiredprivate AuthenticationManager authenticationManager ;@Autowiredprivate RedisConnectionFactory redisConnectionFactory ;/*** 用来配置授权服务器可以为那些客户端授权** @param clients* @throws Exception*/@Overridepublic 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);}@Overridepublic 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: 6379host: 172.16.156.139password: qifengdatabase: 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 {@Autowiredprivate RedisConnectionFactory redisConnectionFactory ;@Overridepublic 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();}@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(inMemoryUsers(bcryptPasswordEncoder()));}@Override@Beanprotected AuthenticationManager authenticationManager() throws Exception {return super.authenticationManager();}@Beanpublic UserDetailsService inMemoryUsers(@Qualifier("bcryptPasswordEncoder") PasswordEncoder encoder) {UserDetails admin = User.withUsername("admin").password(encoder.encode("123")).roles("USER", "ADMIN").build();return new InMemoryUserDetailsManager(admin);}@Overrideprotected 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(//自定义注销urlnew OrRequestMatcher(new AntPathRequestMatcher("/aa", "GET")))//注销成功后跳转页面//.logoutSuccessUrl("/toLogin")//前后端分离时代自定义注销登录处理器.logoutSuccessHandler(new LogoutSuccessHandler() {@Overridepublic 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 {@Overridepublic 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 {@Autowiredprivate PasswordEncoder passwordEncoder;@Autowiredprivate AuthenticationManager authenticationManager;@Autowiredprivate UserDetailsService userDetailsService;/*** 添加第三方的客户端*/@Overridepublic 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*/@Overridepublic 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*/@Beanpublic TokenStore jwtTokenStore() {return new JwtTokenStore(jwtAccessTokenConverter());}@Beanpublic JwtAccessTokenConverter jwtAccessTokenConverter() {JwtAccessTokenConverter tokenConverter = new JwtAccessTokenConverter();tokenConverter.setSigningKey("name");return tokenConverter;}@BeanJwtTokenEnhancer 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 {@Overridepublic void configure(ResourceServerSecurityConfigurer resources) throws Exception {super.configure(resources);resources.resourceId("app").tokenStore(jwtTokenStore()).stateless(true);}@Beanpublic TokenStore jwtTokenStore() {return new JwtTokenStore(jwtAccessTokenConverter());}@Beanpublic 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入门(三)
完结撒花