简介
未更新此文档为spring boot2.2以下版本,因为内容较多,整理更新
Spring Security是一个功能强大且可高度自定义的身份验证和访问控制框架。它是保护基于Spring的应用程序的事实标准。
Spring Security是一个专注于为Java应用程序提供身份验证和授权的框架。与所有Spring项目一样,Spring Security的真正强大之处在于它可以轻松扩展以满足自定义要求
特征
-
对身份验证和授权的全面和可扩展的支持
-
防止会话固定,点击劫持,跨站点请求伪造等攻击
-
Servlet API集成
- 可选与Spring MVC集成
主要介绍:基本登录 ,openId登录,oauth2登录
maven
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
基于注解的认证
@EnableWebSecurity 开启security
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
为基于方法的安全注解
1.securedEnabled = true 一般不使用
开启框架的原生@Secured
注释,spring security底层注解
public interface BankService {
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account readAccount(Long id);
@Secured("IS_AUTHENTICATED_ANONYMOUSLY")
public Account[] findAccounts();
@Secured("ROLE_TELLER")
public Account post(Account account, double amount);
}
2.prePostEnabled = true 使用最多
开启 @PreAuthorize 注解,先大体写个示例
//表现层使用
@GetMapping("create")
@PreAuthorize("hasRole('role_admin')")//hasRole为SpEL表达式
public SysUser create(SysUser sysUser) {
//...
}
//接口中使用(表现层也可以使用)
public interface BankService {
@PreAuthorize("isAnonymous()")
public Account readAccount(Long id);
@PreAuthorize("isAnonymous()")
public Account[] findAccounts();
@PreAuthorize("hasAuthority('ROLE_TELLER')")
public Account post(Account account, double amount);
}
3.jsr250Enabled = true 一般不使用
由java提供JSR是Java Specification Requests的缩写,意思是Java 规范提案。是指向JCP(Java Community Process)提出新增一个标准化技术规范的正式请求。详情:链接
spring boot 配置security
自定义配置信息
@Getter
@Setter
@ConfigurationProperties(prefix = "project.security")//配置前缀
public class SecurityProperties {
// 免认证资源路径
private String[] anonResourcesUrl;
//登录地址
private String loginUrl;
//退出地址
private String logoutUrl;
//持久化登录是否在启动创建表
private boolean createTableOnStartup;
//超时时间默认30分钟
private Duration sessionTimeout = Duration.ofMinutes(30);
//最大并发登录数量,默认值为-1,表示无限制
private int maximumSession;
}
application.yml中添加
#自定义配置
project:
security:
#免认证资源路径
anon-resources-url: /public/**,/static/**
#登录地址
login-url: /login
#退出地址
logout-url: /logout
#30分钟
session-timeout: PT30M
#最大并发登录数量
maximum-session: 1
#持久化登录是否在启动创建表
create-table-on-startup: false
SecurityConfig 配置
响应配置
public class AuthResponseIO extends HashMap<String, Object> {
//200 登录成功
public static AuthResponseIO ok(String msg) {
AuthResponseIO AuthResponse = new AuthResponseIO();
AuthResponse.put("code", HttpStatus.OK.value());
AuthResponse.put("msg", msg);
return AuthResponse;
}
//401 登录未成功
public static AuthResponseIO unAuthorized(String msg) {
AuthResponseIO AuthResponse = new AuthResponseIO();
AuthResponse.put("code", HttpStatus.UNAUTHORIZED.value());
AuthResponse.put("msg", msg);
return AuthResponse;
}
//403
public static AuthResponseIO forbidden() {
AuthResponseIO AuthResponse = new AuthResponseIO();
AuthResponse.put("code", HttpStatus.FORBIDDEN.value());
AuthResponse.put("msg", "无权限访问!");
return AuthResponse;
}
//404
public static AuthResponseIO notFound() {
AuthResponseIO AuthResponse = new AuthResponseIO();
AuthResponse.put("code", HttpStatus.NOT_FOUND.value());
AuthResponse.put("msg", "请求未找到!");
return AuthResponse;
}
//500
public static AuthResponseIO error() {
AuthResponseIO AuthResponse = new AuthResponseIO();
AuthResponse.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
AuthResponse.put("msg", "服务器内部错误!");
return AuthResponse;
}
public static AuthResponseIO error(String msg) {
AuthResponseIO AuthResponse = new AuthResponseIO();
AuthResponse.put("code", HttpStatus.INTERNAL_SERVER_ERROR.value());
AuthResponse.put("msg", msg);
return AuthResponse;
}
@Override
public AuthResponseIO put(String key, Object value) {
super.put(key, value);
return this;
}
}
Security headler配置
/**
* 登录成功处理器
*/
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
WebAuthenticationDetails details = (WebAuthenticationDetails) authentication.getDetails();
//表示接收到身份验证请求的TCP / IP地址
//RemoteAddress()无法伪造,因为建立TCP连接需要三次握手.如果伪造了源 IP,无法建立TCP连接,
//更不会有后面的HTTP请求,如果是代理地址使用下面的getIP。
String remoteAddress = details.getRemoteAddress();
Object principal = authentication.getPrincipal();
if (principal instanceof MyUserDetails) {
MyUserDetails userDetails = (MyUserDetails) principal;
userDetails.setRemoteAddress(remoteAddress);
}
response.setContentType(ResponseContent.JSON_UTF8.getValue());
response.setStatus(HttpStatus.OK.value());
response.getWriter().write(objectMapper.writeValueAsString(AuthResponseIO.ok("登录成功!")));
}
}
/**
* 获取IP地址
* 使用 Ngnix等反向代理软件, 则不能通过request.getRemoteAddr()获取IP地址
* 如果使用了多级反向代理的话,X-Forwarded-For的值并不止一个,而是一串IP地址,X-Forwarded-For中第一个非 unknown的有效IP字符串,则为真实IP地址
*/
public static String getIP(HttpServletRequest request) {
if (Objects.nonNull(request)) {
String ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
ip = request.getHeader("Proxy-Client-IP");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
ip = request.getHeader("WL-Proxy-Client-IP");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
ip = request.getHeader("HTTP_CLIENT_IP");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
ip = request.getHeader("HTTP_X_FORWARDED_FOR");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip))
ip = request.getRemoteAddr();
if ("127.0.0.1".equals(ip) || "0:0:0:0:0:0:0:1".equals(ip))
try {
ip = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
log.error("未知主机异常{}", e.getMessage());
}
return ip;
}
return null;
}
/**
* 登录失败处理器
*/
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
String message;
if (exception instanceof UsernameNotFoundException) {
message = "用户不存在!";
} else if (exception instanceof BadCredentialsException) {
message = "密码错误!";
} else if (exception instanceof LockedException) {
message = "用户已被锁定!";
} else if (exception instanceof DisabledException) {
message = "用户不可用!";
} else if (exception instanceof AccountExpiredException) {
message = "账户已过期!";
} else if (exception instanceof CredentialsExpiredException) {
message = "用户密码已过期!";
} else {
message = "认证失败,请联系网站管理员!";
}
response.setContentType(ResponseContent.JSON_UTF8.getValue());
response.sendError(HttpStatus.UNAUTHORIZED.value(), message);
response.getWriter().write(objectMapper.writeValueAsString(AuthResponseIO.unAuthorized(message)));
}
}
/**
* 访问被拒绝处理
*/
@Component
public class MyAccessDeniedHandler implements AccessDeniedHandler {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
response.setContentType(ResponseContent.JSON_UTF8.getValue());
response.getWriter().write(objectMapper.writeValueAsString(AuthResponseIO.forbidden()));
response.sendError(HttpStatus.FORBIDDEN.value(),"无权限访问!");
}
//不分离配置
// private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
//
// @Override
// public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
// response.setContentType("application/json;charset=utf-8");
// response.getWriter().write(objectMapper.writeValueAsString(AuthResponseIO.forbidden()));
// response.sendError(HttpStatus.FORBIDDEN.value(), HttpStatus.FORBIDDEN.getReasonPhrase());
// redirectStrategy.sendRedirect(request, response, "/403.html");
// }
}
/**
* 配置退出处理器,解决登出后 principals 中还存在相应的 sessionInformation 的问题
*/
@Component
public class MyLogoutHandler implements LogoutHandler {
@Autowired
private ApplicationContext applicationContext;
@Override
public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {
String sessionId = request.getRequestedSessionId();
if (Objects.nonNull(sessionId)) {
SessionRegistry sessionRegistry = applicationContext.getBean(SessionRegistry.class);
sessionRegistry.removeSessionInformation(sessionId);
}
}
}
/**
* 退出成功处理器
*/
@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.setContentType(ResponseContent.JSON_UTF8.getValue());
response.setStatus(HttpStatus.OK.value());
response.getWriter().write(objectMapper.writeValueAsString(AuthResponseIO.ok("退出成功!")));
}
}
自定义状态码页面
@Configuration
public class ErrorPageConfig implements ErrorPageRegistrar {
@Override
public void registerErrorPages(ErrorPageRegistry registry) {
List<ErrorPage> errorPages = new ArrayList<>();
errorPages.add(new ErrorPage(HttpStatus.NOT_FOUND, "/404.html"));
errorPages.add(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/500.html"));
registry.addErrorPages(errorPages.toArray(new ErrorPage[0]));
}
}
security bean 配置
将需要注入的bean单独配置
使用密码加密参考:链接
@Configuration
public class SecurityBean {
/**
* HTTP 防火墙可防止HTTP动词篡改和跨站点跟踪
* 参考 <a href="https://www.owasp.org/index.php/Test_HTTP_Methods_(OTG-CONFIG-006)">
*
* @return StrictHttpFirewall
*/
@Bean
public StrictHttpFirewall httpFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
//HTTP方法白名单跨站跟踪(XST)和HTTP动词篡改
firewall.setAllowedHttpMethods(Arrays.asList(HttpMethod.GET.name(), HttpMethod.POST.name()));
return firewall;
}
/**
* session会话注册表
*
* @return SessionRegistry
*/
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Autowired
private SecurityProperties securityProperties;
/**
* 处理 rememberMe 自动登录
*/
@Bean
public PersistentTokenRepository persistentTokenRepository() {
JdbcTokenRepositoryImpl jdbcTokenRepository = new JdbcTokenRepositoryImpl();
// 获取配置的数据源
DataSource dataSource = applicationContext.getBean(DataSource.class);
jdbcTokenRepository.setDataSource(dataSource);
//设置启动时创建表
jdbcTokenRepository.setCreateTableOnStartup(securityProperties.isCreateTableOnStartup());
return jdbcTokenRepository;
}
@Autowired
private ApplicationContext applicationContext;
/**
* 密码策略为默认,为了安全希望自定义
* 参考:PasswordEncoderFactories.createDelegatingPasswordEncoder();
* 密码策略put的内容为都支持的算法,不安全的算法最好不要支持
* @return PasswordEncoder
*/
@Bean
public PasswordEncoder passwordEncoder() {
return PasswordEncoderFactories.createDelegatingPasswordEncoder();
}
/**
* 重定向策略
*
* @return RedirectStrategy
*/
@Bean
public RedirectStrategy redirectStrategy() {
return new DefaultRedirectStrategy();
}
/**
* Dao认证提供商
*
* @return DaoAuthenticationProvider
*/
@Bean
public DaoAuthenticationProvider daoAuthenticationProvider() {
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
provider.setHideUserNotFoundExceptions(false);
UserDetailsService userDetailsService = applicationContext.getBean(MyUserDetailsService.class);
provider.setUserDetailsService(userDetailsService);
provider.setPasswordEncoder(passwordEncoder());
return provider;
}
}
UserDetails
@Getter
@Setter
public class MyUserDetails extends User {
//以后第三方登录会使用
private LoginType loginType = LoginType.NORMAL;
private String userId;
private String remoteAddress;
public MyUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
public MyUserDetails(String username, String password, boolean enabled, boolean accountNonExpired, boolean credentialsNonExpired, boolean accountNonLocked, Collection<? extends GrantedAuthority> authorities) {
super(username, password, enabled, accountNonExpired, credentialsNonExpired, accountNonLocked, authorities);
}
}
@Getter
public enum LoginType {
NORMAL, SMS, SOCIAL
}
userDetailsService
@Component
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private ApplicationContext applicationContext;
/**
* 需要的数据参考{@link User#withUserDetails}
*
* @param username username
* @return UserDetails
* @throws UsernameNotFoundException e
*/
@Override
public UserDetails loadUserByUsername(String username) {
//SysUser,SysRole,SysPermission为实体类
//逻辑层SysUserService
SysUserService sysUserService = applicationContext.getBean(SysUserService.class);
//查询
SysUser sysUser = sysUserService.findByLoginName(username);
if (Objects.nonNull(sysUser)) {
//获取角色
Set<SysRole> sysRoles = sysUser.getSysRoles();
String[] roleCode = new String[]{};
String[] perCode = new String[]{};
if (Objects.nonNull(sysRoles) && !sysRoles.isEmpty()) {
//角色必须以ROLE_开头 例如ROLE_ADMIN
roleCode = sysRoles.stream().filter(sysRole -> Objects.nonNull(sysRole.getRoleCode())).map(SysRole::getRoleCode).distinct().toArray(String[]::new);
//具体的权限 看不懂参考jdk8-lambda表达式
List<SysPermission> permissions = sysRoles.stream().
filter(sysRole -> Objects.nonNull(sysRole.getSysPermissions()) && !sysRole.getSysPermissions().isEmpty())
.map(SysRole::getSysPermissions).flatMap(Collection::stream).collect(Collectors.toList());
perCode = permissions.stream().filter(o -> Objects.nonNull(o.getPerCode()))
.map(SysPermission::getPerCode).distinct().toArray(String[]::new);
}
List<GrantedAuthority> roles = AuthorityUtils.createAuthorityList(roleCode);
List<GrantedAuthority> permissions = AuthorityUtils.createAuthorityList(perCode);
roles.addAll(permissions);
MyUserDetails myUserDetails = new MyUserDetails(sysUser.getLoginName(), sysUser.getPassword(),
sysUser.isEnabled(), sysUser.isAccountNonExpired(),
sysUser.isCredentialsNonExpired(), sysUser.isAccountNonLocked(),
roles);
myUserDetails.setUserId(sysUser.getUserId());
return myUserDetails;
} else {
throw new UsernameNotFoundException("未知用户异常!");
}
}
}
security config 配置
/**
* 了解 跨站请求伪造(CSRF)攻击,动词篡改和跨站点跟踪
*/
@EnableWebSecurity
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableConfigurationProperties(SecurityProperties.class)
@Import({MyUserDetailsService.class, SecurityBean.class, MyAccessDeniedHandler.class, MyAuthenticationSuccessHandler.class,
MyAuthenticationFailureHandler.class, MyLogoutHandler.class,
MyLogoutSuccessHandler.class, MyInvalidSessionStrategy.class,
MySessionInformationExpiredStrategy.class})
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private SecurityProperties securityProperties;
/**
* 配置忽略路径
*
* @param web WebSecurity
* @throws Exception e
*/
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().antMatchers(securityProperties.getAnonResourcesUrl());
}
@Autowired
private MyUserDetailsService myUserDetailsService;
@Autowired
private MyAccessDeniedHandler myAccessDeniedHandler;
@Autowired
private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
@Autowired
private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
@Autowired
private MyLogoutHandler myLogoutHandler;
@Autowired
private MyLogoutSuccessHandler myLogoutSuccessHandler;
@Autowired
private MyInvalidSessionStrategy myInvalidSessionStrategy;
@Autowired
private MySessionInformationExpiredStrategy mySessionInformationExpiredStrategy;
@Autowired
private SessionRegistry sessionRegistry;
@Autowired
private PersistentTokenRepository persistentTokenRepository;
/**
* 权限认证
*
* @param http http
* @throws Exception e
* @<code> 也可以配置免认证资源:.antMatchers("/resources/**", "/public", "/about").permitAll()。permitAll为任何用户都可以请求访问 </code>
* @<code> 登录跳转页面:loginPage() </code>
* @<code> 登录成功跳转:successForwardUrl() </code>
* @<code> 可选项:启用会话URL重写(默认false,慎用):enableSessionUrlRewriting()</code>
* @<code> 页面使用iframe 禁用http.headers().frameOptions().disable()</code>
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
/*全部请求需认证*/
http.authorizeRequests().anyRequest().authenticated()
.and()
/*访问被拒绝处理器*/
.exceptionHandling().accessDeniedHandler(myAccessDeniedHandler)
.and()
/*登录*/
.formLogin()
.loginPage("/login.html")
.successForwardUrl("index.html")
.loginProcessingUrl(securityProperties.getLoginUrl())
//处理登录成功
.successHandler(myAuthenticationSuccessHandler)
//处理登录失败
.failureHandler(myAuthenticationFailureHandler)
//任何用户都可以请求访问
.permitAll()
.and()
/*添加记住我功能(自动登录)*/
.rememberMe()
.tokenRepository(persistentTokenRepository) // 配置 token 持久化仓库
.tokenValiditySeconds((int) securityProperties.getSessionTimeout().getSeconds()) // rememberMe 过期时间,单为秒
.userDetailsService(myUserDetailsService)
.and()
/*配置 session管理器*/
.sessionManagement()
//session过期策略
.invalidSessionStrategy(myInvalidSessionStrategy)
//session过期跳转url
.invalidSessionUrl(securityProperties.getLoginUrl())
//最大并发登录数量
.maximumSessions(securityProperties.getMaximumSession())
//最大会话阻止登录(第二次登录操作,默认false第二次登录踢出第一次,true禁止第二次登录)
.maxSessionsPreventsLogin(false)
//处理并发登录被踢出
.expiredSessionStrategy(mySessionInformationExpiredStrategy)
.expiredUrl(securityProperties.getLoginUrl())
//配置session注册中心
.sessionRegistry(sessionRegistry)
.and().and()
/*退出*/
.logout()
//退出时处理器
.addLogoutHandler(myLogoutHandler)
//退出成功处理器
.logoutSuccessHandler(myLogoutSuccessHandler)
.logoutUrl(securityProperties.getLogoutUrl())
.logoutSuccessUrl(securityProperties.getLoginUrl())
//使Http会话无效
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID")
.permitAll()
.and()
.httpBasic();
}
@Autowired
private DaoAuthenticationProvider daoAuthenticationProvider;
/**
* 认证管理生成器
*
* @param auth 认证
* @throws Exception e
*/
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(daoAuthenticationProvider);
}
}
登录退出controller不需要配置
注解使用
@EnableGlobalMethodSecurity(prePostEnabled = true) 上面配置有开启
获取登录的信息
public static MyUserDetails getCurrentUser() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
Object principal = authentication.getPrincipal();
if (principal instanceof MyUserDetails) {
return (MyUserDetails) principal;
}
return null;
}
使用的是SpEL表达式访问Authentication.getPrincipal()
表达式 | 描述 |
---|---|
|
|
|
返回 |
|
|
|
返回 |
|
允许直接访问代表当前用户的主体对象 |
|
允许直接访问从中获取的当前 |
|
始终评估为 |
|
始终评估为 |
|
|
|
返回 |
|
|
|
返回 |
|
|
|
|
数据角色表role 添加 字段role_code 为ROLE_ADMIN
包含的权限表permission添加字段per_code 为:user:create
和用户配置下关系
注意:在MyUserDetailsService配置中AuthorityUtils.createAuthorityList() 其实按官方是用来配置角色的,我配置为角色和权限。 这样使用有一个问题那就是权限修改无法同步到已登录用户的session中,解决参考下一篇: security JWT配置。
- hasAuthority:
可以读取全部的内容,hasAuthority读取角色需要写前缀
@RequestMapping("test2")
@PreAuthorize("hasAuthority('user:create')")
public String t2(){
return "菜单权限认证成功";
}
@RequestMapping("test1")
@PreAuthorize("hasAuthority('ROLE_ADMIN')")
public String t(){
return "角色权限认证成功";
}
- hasRole:
读取角色必须以ROLE_开头认为是角色,所以角色读取去掉前缀。
@RequestMapping("test1")
@PreAuthorize("hasRole('ADMIN')")
public String t(){
return "角色权限认证成功";
}
还可以这样使用 @PreAuthorize("hasRole('USER') or hasRole('ADMIN')")
- hasPermission:
官方配置需要域对象安全性(ACL)读取权限,还需依赖
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-acl</artifactId>
</dependency>
使用起来比较麻烦采用自定义。
1. 修改MyUserDetailsService
/**
* 需要的数据参考{@link User#withUserDetails}
*
* @param username username
* @return UserDetails
* @throws UsernameNotFoundException e
*/
@Override
public UserDetails loadUserByUsername(String username) {
SysUserService sysUserService = applicationContext.getBean(SysUserService.class);
SysUser sysUser = sysUserService.findByLoginName(username);
if (Objects.nonNull(sysUser)) {
Set<SysRole> sysRoles = sysUser.getSysRoles();
String[] roleCode = new String[]{};
if (Objects.nonNull(sysRoles) && !sysRoles.isEmpty()) {
//数据库角色码必须以ROLE_开头
roleCode = sysRoles.stream().filter(sysRole -> Objects.nonNull(sysRole.getRoleCode())).map(SysRole::getRoleCode).distinct().toArray(String[]::new);
}
MyUserDetails myUserDetails = new MyUserDetails(sysUser.getLoginName(), sysUser.getPassword(),
sysUser.isEnabled(), sysUser.isAccountNonExpired(),
sysUser.isCredentialsNonExpired(), sysUser.isAccountNonLocked(),
AuthorityUtils.createAuthorityList(roleCode));
myUserDetails.setUserId(sysUser.getUserId());
return myUserDetails;
} else {
throw new UsernameNotFoundException("未知用户异常!");
}
}
}
/**
* hasPermission自定义注解 @PreAuthorize("hasPermission()")的hasPermission认证内容
* fixme:使用注意修改 {@link MyUserDetailsService#loadUserByUsername(String)} 去掉权限码
*/
@Configuration
public class MyPermissionEvaluator implements PermissionEvaluator {
@Autowired
private ApplicationContext applicationContext;
/**
* 这样使用的会有一个问题就是每次需要查询(可以解决权限同步)
*
* @param authentication Authentication
* @param prefix 前缀
* @param suffix 后缀
* @return boolean
*/
@Override
public boolean hasPermission(Authentication authentication, Object prefix, Object suffix) {
SysUserService userService = applicationContext.getBean(SysUserService.class);
boolean flag = false;
//获取登录后校验的用户数据
Object principal = authentication.getPrincipal();
//判断是否是MyUserDetails的实例(会有其它类型的登录例如oauth2)
if (principal instanceof MyUserDetails) {
MyUserDetails userDetails = (MyUserDetails) principal;
//查询
SysUser user = userService.findByLoginName(userDetails.getUsername());
if (Objects.nonNull(user)) {
Set<SysRole> sysRoles = user.getSysRoles();
if (Objects.nonNull(sysRoles) && !sysRoles.isEmpty()) {
List<String> perCodes = sysRoles.stream().map(SysRole::getSysPermissions)
.filter(Objects::nonNull).flatMap(Collection::stream)
.map(SysPermission::getPerCode).distinct().collect(Collectors.toList());
if (Objects.nonNull(perCodes) && !perCodes.isEmpty()) {
if (Objects.nonNull(prefix) && Objects.nonNull(suffix)) {
String code = prefix.toString() + ":" + suffix.toString();
for (String perCode : perCodes) {
if (Objects.equals(code, perCode)) {
flag = true;
break;
}
}
}
}
}
}
}
return flag;
}
//未使用调用返回false,认证失败
@Override
public boolean hasPermission(Authentication authentication, Serializable targetId, String targetType, Object permission) {
return false;
}
}
例如:
@RequestMapping("test3")
@PreAuthorize("hasPermission('user','create')")
public String t3(){
return "认证成功";
}
下一篇: security JWT配置(暂更)