前言
第一次使用spring security,前后端分离方式。前端vue+element,后端springboot。
搜索了一些文档,基础的登录功能已经实现。
- spring security配置之WebSercurityConfig
package com.website.server.system.security;
import com.website.server.system.security.hander.LoginFailureHandler;
import com.website.server.system.security.hander.LoginSuccessHandler;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
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;
/**
* @author :qilong sun
* @date :Created in 2019/11/27 16:56
* @description:security配置
* @modified By:
* @version: V1.0$
*/
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
// 开启授权认证
httpSecurity.authorizeRequests().anyRequest().authenticated();
// 配置登录
httpSecurity.formLogin().usernameParameter("loginAccount").passwordParameter("loginPwd").loginProcessingUrl("/toLogin");
// 登录成功处理
httpSecurity.formLogin().successHandler(new LoginSuccessHandler());
// 登录失败处理
httpSecurity.formLogin().failureHandler(new LoginFailureHandler());
// csrf配置
httpSecurity.csrf();
// 开启跨域共享,跨域伪造请求限制=无效
httpSecurity.cors().and().csrf().disable();
}
}
- spring security之自定义userDetailsService
package com.website.server.system.security;
import com.core.server.dto.security.SecurityPermission;
import com.core.server.dto.security.SecurityRoleDto;
import com.core.server.dto.security.SecurityUserDto;
import com.core.server.service.api.user.BaseUserFeignServiceApi;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
/**
* @author :qilong sun
* @date :Created in 2019/11/27 14:00
* @description:用户权限;参考教程:https://blog.csdn.net/cloume/article/details/83790111
* @modified By:
* @version: V1.0$
*/
@Service
public class MyUserDetailService implements UserDetailsService {
@Autowired
BaseUserFeignServiceApi baseUserFeignServiceApi;
@Override
public UserDetails loadUserByUsername(String username) {
// 根据用户名查询用户
SecurityUserDto user = baseUserFeignServiceApi.querySecurityUserDtoByOne(username);
if (null == user) {
throw new UsernameNotFoundException(String.format("No user found with username: %s", username));
}
List<SimpleGrantedAuthority> authorities = new ArrayList<>();
List<String> roles = user.getRoles();
if (null != roles) {
for (String roleName : roles) {
// 根据角色名称查询角色
SecurityRoleDto securityRoleDto = baseUserFeignServiceApi.querySecurityRoleDtoByCodeName(roleName);
if (null == securityRoleDto || null == securityRoleDto.getPermissions() || securityRoleDto.getPermissions().size() == 0) {
continue;
}
for (SecurityPermission.ResourcesDto permission : securityRoleDto.getPermissions()) {
for (String privilege : permission.getPrivileges().keySet()) {
authorities.add(new SimpleGrantedAuthority(String.format("%s-%s", permission.getResourceId(), privilege)));
}
}
}
}
// isEnabled=是否启用; isAccountNonExpired=账户是否过期; isCredentialsNonExpired=凭证是否过期; isAccountNonLocked=账户是否锁定
return new org.springframework.security.core.userdetails.User(user.getUsername(), "{noop}" + user.getPassword(), user.getIsEnabled(), user.getIsAccountNonExpired(), user.getIsCredentialsNonExpired(), user.getIsAccountNonLocked(), authorities);
}
}
- 登录
- 获取用户信息
{
"authorities": [
{
"authority": "privilege-delete"
},
{
"authority": "privilege-read"
},
{
"authority": "privilege-update"
},
{
"authority": "privilege-write"
},
{
"authority": "user-delete"
},
{
"authority": "user-read"
},
{
"authority": "user-update"
},
{
"authority": "user-write"
}
],
"details": {
"remoteAddress": "0:0:0:0:0:0:0:1",
"sessionId": null
},
"authenticated": true,
"principal": {
"password": null,
"username": "1441101265",
"authorities": [
{
"authority": "privilege-delete"
},
{
"authority": "privilege-read"
},
{
"authority": "privilege-update"
},
{
"authority": "privilege-write"
},
{
"authority": "user-delete"
},
{
"authority": "user-read"
},
{
"authority": "user-update"
},
{
"authority": "user-write"
}
],
"accountNonExpired": true,
"accountNonLocked": true,
"credentialsNonExpired": true,
"enabled": true
},
"credentials": null,
"name": "1441101265"
}
目标
现在想把spring security抽取出来,做成通用的。
方法:
- 做成一个jar
做成jar的话,和直接写在一个项目里效果一样。 - 做成服务
做成服务的话,还可以自定义一些其他接口,使用的相对更灵活一点,难度相对也更大点。
思路
上面已经介绍了是怎么实现的。如果前端登录,会走userDetailsService,查询用户信息,用户角色,用户权限等。然后把结果,通过处理器,封装后,返回给前台,这就是登录流程。
下面开始做。
首先,项目已经建好,依赖已经加好,代码,也已经复制到项目里面。
- aplication.yml,里面配置是端口,以及注册中心信息
- ***Application是启动类
- system.security下面是上面图片里面的代码。hander是成功处理,失败处理。另外就是filter(具体干嘛用的,好像加它是因为处理器还是啥的,记不清了,后面需要的时候再查下)。
变动代码:webSecurityConfig,因为现在是通过服务方式调用,按照上面的配置,我是需要先登录的。所以现在先把拦截都放开,至于是否需要配置,后面再说。否则测试的时候,调接口都调不通。这是现在的代码:
/**
* @author :qilong sun
* @date :Created in 2019/11/27 16:56
* @description:security配置
* @modified By:
* @version: V1.0$
*/
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests().antMatchers(
"/js/**",
"/css/**",
"/img/**",
"/login/**").permitAll()
.anyRequest().permitAll();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService());
}
}
- SecurityController,控制器。专门提供接口的。
现在,我把userDetailsService注入到controller里面。然后调用登录的方法。
package com.core.server.controller;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.security.Principal;
/**
* @author :qilong sun
* @date :Created in 2019/12/20 19:42
* @description:security控制器
* @modified By:
* @version: $1.0
*/
@RestController
@RequestMapping("/security")
public class SecurityController {
@Resource
UserDetailsService myUserDetailServiceImpl;
/**
* 测试获取用户信息
* @return
*/
@GetMapping("/testGetUser2")
public Principal getUser2(HttpServletRequest request) {
return request.getUserPrincipal();
}
/**
* 根据账号登录
* @param username
* @return
*/
@GetMapping("/loginByUsername")
public UserDetails loginByUsername(@RequestParam("username") String username){
UserDetails userDetails = myUserDetailServiceImpl.loadUserByUsername(username);
return userDetails;
}
}
整个项目,代码如上。
通过postman,现在是可以跑到这个loginByUsername方法。返回信息如下:
问题:如果security没有做成服务,那么调用登录,是会在cookie里面生成一个jssessionid的,返回给前台,放到请求信息里面,然后前台每次请求的时候,都带上它。这个jssessionid代表了当前用户,包含了用户信息,角色信息,权限信息等。当然,上图返回的信息比较少。是因为登录后,如果获取用户信息,一般是通过这个方法获取的,上面postman返回的就是:
/**
* 测试获取用户信息
* @return
*/
@GetMapping("/testGetUser2")
public Principal getUser2(HttpServletRequest request) {
return request.getUserPrincipal();
}
可是现在通过myUserDetailServiceImpl.loadUserByUsername(username); 是没有token的。返回的是有资源信息,但是这是在security服务。把它返回到消费者服务,怎么让controller里方法上面的 @PreAuthorize(“hasRole(‘user’)”) 注解生效,没有思路。
参考文档
待补充
解决方法
待补充
结果
待补充
遇到的问题