spring boot整合spring security实现基于rabc的权限控制
程序员文章站
2024-03-26 09:36:05
...
1.spring security基于rbac权限控制
1.1 引入依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.mayday</groupId>
<artifactId>mayday_common</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-spring-boot-starter</artifactId>
<version>2.1.5</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1.2配置文件
server:
port: 9003
spring:
application:
name: mayday-auth
datasource:
driverClassName: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/blog?useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: admin
redis:
host: 127.0.0.1
eureka:
client:
serviceUrl:
defaultZone: http://127.0.0.1:9001/eureka/
instance:
prefer-ip-address: true
mybatis:
type-aliases-package: com.mayday.auth.pojo
configuration:
map-underscore-to-camel-case: true
rsa:
key:
pubKeyFile: D:/document/key/key_rsa.pub
priKeyFile: D:/document/key/key_rsa
allowPaths:
- /auth/login/**
- /auth/code/**
1.3 创建pojo类
@Data
@Entity
@Table(name = "tb_user")
public class User implements UserDetails {
@Id
@KeySql(useGeneratedKeys = true)
private Long id;
private String username;
private String password;
private Boolean status;
private String phone;
@Transient
private String checkCode;
private List<Role> roles;
@Override
@JsonIgnore
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles.stream().map(Role::getPermissions).findAny().get();
}
@Override
@JsonIgnore
public boolean isAccountNonExpired() {
return true;
}
@Override
@JsonIgnore
public boolean isAccountNonLocked() {
return true;
}
@Override
@JsonIgnore
public boolean isCredentialsNonExpired() {
return true;
}
@Override
@JsonIgnore
public boolean isEnabled() {
return true;
}
}
@Data
@Entity
@Table(name = "tb_role")
public class Role {
@Id
@KeySql(useGeneratedKeys = true)
private Long id;
@Column(name = "role_name")
private String roleName;
@Column(name = "role_desc")
private String roleDesc;
private List<Permission> permissions;
}
@Data
@Entity
@Table(name = "tb_permission")
public class Permission implements GrantedAuthority {
@Id
@KeySql(useGeneratedKeys = true)
private Long id;
@Column(name = "permission_name")
private String permissionName;
@Column(name = "permission_desc")
private String permissionDesc;
@Override
@JsonIgnore
public String getAuthority() {
return permissionName;
}
}
1.4 编写dao和service层
DAO层
user接口
public interface UserMapper extends Mapper<User> {
}
role接口
public interface RoleMapper extends Mapper<Role> {
@Select("select r.id,r.role_name,r.role_desc from tb_role r left join tb_user_role ur on r.id = ur.role_id where ur.user_id = #{userId}")
List<Role> selectAllByUserId(Long userId);
}
permission接口
public interface PermissionMapper extends Mapper<Permission> {
@Select("select p.id,p.permission_name,p.permission_desc from tb_permission p left join tb_role_permission rp on p.id = rp.permission_id where rp.role_id = #{roleId}")
List<Permission> selectAllByRoleId(Long roleId);
}
service 层
@Service
public class UserService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Autowired
private PermissionMapper permissionMapper;
@Autowired
private RoleMapper roleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = new User();
user.setUsername(username);
User currentUser = userMapper.selectOne(user);
if (currentUser == null) {
return null;
}
//设置用户的角色
List<Role> roles = roleMapper.selectAllByUserId(currentUser.getId());
if (!CollectionUtils.isEmpty(roles)) {
for (Role role : roles) {
//查询每个角色对应的权限
List<Permission> permissions = permissionMapper.selectAllByRoleId(role.getId());
if (!CollectionUtils.isEmpty(permissions)) {
role.setPermissions(permissions);
}
}
currentUser.setRoles(roles);
}
return currentUser;
}
}
1.5 controller层
@RestController
@RequestMapping("/auth")
public class UserController {
@Autowired
private UserService userService;
@Autowired
private StringRedisTemplate redisTemplate;
@Autowired
private RsaKeyProperties rsaKeyProperties;
private static final String REDIS_PREFIX = "mayday:login:code";
private static final int OUT_TIME = 30;
private static final String TOKEN_PREFIX = "Bearer ";
/**
* 生成验证码
*
* @param response
* @param request
* @throws IOException
*/
@GetMapping("/code")
public void captcha(HttpServletResponse response, HttpServletRequest request) throws IOException {
response.setHeader("Cache-Control", "no-store, no-cache");
response.setContentType("image/jpeg");
// 生成图片验证码
BufferedImage image = CaptchaUtil.createImage();
// 生成文字验证码
String randomText = CaptchaUtil.drawRandomText(image);
// 保存到验证码到 redis 有效期两分钟
redisTemplate.opsForValue().set(REDIS_PREFIX, randomText.toLowerCase(), 1, TimeUnit.MINUTES);
ServletOutputStream out = response.getOutputStream();
ImageIO.write(image, "jpeg", out);
}
/**
* 登录
*
* @param user
* @return
*/
@PostMapping("/login")
public Result login(@RequestBody User user) {
//检查验证码是否正确
String checkCode = user.getCheckCode();
String redisCode = redisTemplate.opsForValue().get(REDIS_PREFIX);
if (StringUtils.isEmpty(checkCode) || !redisCode.equals(checkCode)) {
return new Result(false, StatusCode.CHECK_CODE_NOT_EXIT.getCode(), StatusCode.CHECK_CODE_NOT_EXIT.getMsg());
}
//验证登录
User currentUser = (User) userService.loadUserByUsername(user.getUsername());
if (currentUser == null) {
return new Result(false, StatusCode.USERNAME_NOT_FOUND.getCode(), StatusCode.USERNAME_NOT_FOUND.getMsg());
}
//检验密码是否输入正确
if (new BCryptPasswordEncoder().matches(user.getPassword(), currentUser.getPassword())) {
return new Result(false, StatusCode.LOGIN_ERROR.getCode(), StatusCode.LOGIN_ERROR.getMsg());
}
String token = TOKEN_PREFIX + JwtUtils.generateTokenExploreInMinutes(currentUser, rsaKeyProperties.getPrivateKey(), OUT_TIME);
return new Result(true, StatusCode.OK.getCode(), StatusCode.OK.getMsg(), token);
}
/**
* 权限测试接口
* @return
*/
@GetMapping("test")
@PreAuthorize("hasAnyAuthority('product_list')")
public String test() {
return "test success";
}
}
2 配置
2.1 创建配置类
@Data
@ConfigurationProperties("rsa")
public class AllowPathsProperties {
private List<String> allowPaths;
}
@Data
@ConfigurationProperties("rsa.key")
public class RsaKeyProperties {
private String pubKeyFile;
private String priKeyFile;
public PublicKey publicKey;
private PrivateKey privateKey;
/**
* 在创建对象之后执行的方法
*
* @throws Exception
*/
@PostConstruct
public void createRsaKey() throws Exception {
publicKey = RsaUtils.getPublicKey(pubKeyFile);
privateKey = RsaUtils.getPrivateKey(priKeyFile);
}
}
创建Security配置类
@Slf4j
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private UserService userService;
@Autowired
private RsaKeyProperties rsaKeyProperties;
@Autowired
private AllowPathsProperties allowPathsProperties;
/**
* 解决无法直接注入AuthenticationManager
*
* @return
* @throws Exception
*/
@Bean
@Override
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
/**
* 装载BCrypt密码编码器 密码加密
*
* @return
*/
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// 设置UserDetailsService 使用BCrypt进行密码的hash
auth.userDetailsService(userService).passwordEncoder(this.passwordEncoder());
}
@Override
public void configure(HttpSecurity http) throws Exception {
http
// 由于使用的是JWT,我们这里不需要csrf
.csrf().disable()
// 过滤请求
.authorizeRequests()
// 对于登录login 图标 要允许匿名访问
.antMatchers(allowPathsProperties.getAllowPaths().stream().toArray(String[]::new)).anonymous()
// 除上面外的所有请求全部需要鉴权认证
.anyRequest().authenticated()
.and()
.headers().frameOptions().disable()
.and().addFilter(new LoginFilter(super.authenticationManager(), rsaKeyProperties))
//基于token,不需要session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
;
}
}
2.2创建过滤器
public class LoginFilter extends BasicAuthenticationFilter {
private RsaKeyProperties rsaKeyProperties;
public LoginFilter(AuthenticationManager authenticationManager, RsaKeyProperties rsaKeyProperties) {
super(authenticationManager);
this.rsaKeyProperties = rsaKeyProperties;
}
@Override
public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String header = request.getHeader("Authorization");
if (header == null || !header.startsWith("Bearer ")) {
//如果携带错误的token,则给用户提示请登录!
chain.doFilter(request, response);
response.setContentType("application/json;charset=utf-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
PrintWriter out = response.getWriter();
Map resultMap = new HashMap();
resultMap.put("code", HttpServletResponse.SC_FORBIDDEN);
resultMap.put("msg", "请登录!");
out.write(new ObjectMapper().writeValueAsString(resultMap));
out.flush();
out.close();
} else {
//如果携带了正确格式的token要先得到token
String token = header.replace("Bearer ", "");
//验证token是否正确
Payload<User> payload = JwtUtils.genInfoFromToken(token, rsaKeyProperties.getPublicKey(), User.class);
User user = payload.getUserInfo();
if (user != null) {
UsernamePasswordAuthenticationToken authResult = new UsernamePasswordAuthenticationToken(user.getUsername(), null, user.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authResult);
chain.doFilter(request, response);
}
}
}
}
3启动类
@SpringBootApplication
@MapperScan("com.mayday.auth.dao")
@EnableDiscoveryClient
@EnableConfigurationProperties({RsaKeyProperties.class, AllowPathsProperties.class})
public class AuthApplication {
public static void main(String[] args) {
SpringApplication.run(AuthApplication.class);
}
}
4自定义异常处理
/**
* 认证失败处理类 返回401
*
* @author lihaodong
*/
@Slf4j
@Component
public class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) throws IOException {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
//如果携带错误的token,则给用户提示请登录!
PrintWriter out = response.getWriter();
Result result = new Result(false, StatusCode.UN_AUTHORIZATION);
out.write(new ObjectMapper().writeValueAsString(result));
out.flush();
out.close();
}
}
/**
* 权限不足认证
*/
@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
httpServletResponse.setStatus(HttpServletResponse.SC_FORBIDDEN);
//如果携带错误的token,则给用户提示请登录!
PrintWriter out = httpServletResponse.getWriter();
Result result = new Result(false, StatusCode.ACCESS_ERROR);
out.write(new ObjectMapper().writeValueAsString(result));
out.flush();
out.close();
}
}
在webSecurityConfig配置类上加上
@Autowired
private AuthenticationEntryPointImpl authenticationEntryPoint;
@Autowired
private AccessDeniedHandlerImpl accessDeniedHandler;
@Override
public void configure(HttpSecurity http) throws Exception {
http
// 由于使用的是JWT,我们这里不需要csrf
.csrf().disable()
//认证失败处理类
.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler)
}
}
5 测试
将生成的token填入测试接口中
推荐阅读
-
spring boot整合spring security实现基于rabc的权限控制
-
SpringBoot+Spring Security+JWT实现RESTful Api权限控制的方法
-
SpringBoot+Spring Security+JWT实现RESTful Api权限控制的方法
-
基于Spring Security前后端分离的权限控制系统问题
-
jwt,spring security ,feign,zuul,eureka 前后端分离 整合 实现 简单 权限管理系统 与 用户认证的实现
-
Springboot+Spring Security实现前后端分离登录认证及权限控制的示例代码
-
《Spring Security3》第五章第二部分翻译下(实现授权精确控制的方法——页面级权限)
-
《Spring Security3》第五章第二部分翻译上(实现授权精确控制的方法——页面级权限)
-
Spring Boot整合Spring Security实现权限控制
-
实现基于Spring框架应用的权限控制系统security