1 github: 源码地址 2 security02 子工程查询数据库,获取用户信息,赋予用户角色交给Security管理
自定义登录页面、无权访问跳转页面
跨域
4.0.0 com.yzm security 0.0.1-SNAPSHOT ../pom.xml security02 0.0.1-SNAPSHOT security02 jar Demo project for Spring Boot com.yzm common 0.0.1-SNAPSHOT org.springframework.boot spring-boot-maven-plugin
项目结构
application.yml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test04?useUnicode=true&characterEncoding=utf8&useSSL=false&allowMultiQueries=true&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai
username: root
password: root
mybatis-plus:
mapper-locations: classpath:/mapper
@Service
public class SecUserDetailsServiceImpl implements UserDetailsService {
private final UserService userService;
private final RoleService roleService;
public SecUserDetailsServiceImpl(UserService userService, RoleService roleService) {
this.userService = userService;
this.roleService = roleService;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userService.lambdaQuery().eq(User::getUsername, username).one();
if (user == null) {
throw new UsernameNotFoundException(String.format("用户'%s'不存在", username));
}
List roleIds = Arrays.stream(user.getRIds().split(","))
.map(Integer::parseInt)
.collect(Collectors.toList());
List roleList = roleService.listByIds(roleIds);
List authorities = roleList.stream()
.map(Role::getRName)
.distinct()
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
// 这里我们的权限只是基于角色的
return new org.springframework.security.core.userdetails.User(username, user.getPassword(), authorities);
}
}
5 SecurityConfigloadUserByUsername 返回的 UserDetails 对象封装了用户信息,但它是一个接口
Security给我们默认提供了org.springframework.security.core.userdetails.User,所以使用new User()返回
把自定义的UserDetailsService交给Security管理,以便Security查询数据
自定义了登录页面和无权的响应页面
设置url拦截管理
package com.yzm.security02.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Slf4j
@Configuration
@EnableWebSecurity // 开启Security服务
@EnableGlobalMethodSecurity(prePostEnabled = true) // 开启全局注解
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private final UserDetailsService userDetailsService;
public SecurityConfig(@Qualifier("secUserDetailsServiceImpl") UserDetailsService userDetailsService) {
this.userDetailsService = userDetailsService;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// 从数据库读取用户、并使用密码编码器解密
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
//配置资源权限规则
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// 关闭CSRF跨域
.csrf().disable()
// 登录
.formLogin()
.loginPage("/auth/login") //指定登录页的路径,默认/login
.loginProcessingUrl("/login") //指定自定义form表单请求的路径(必须跟login.html中的form action=“url”一致)
.defaultSuccessUrl("/home", true) // 登录成功后的跳转url地址
.failureUrl("/auth/login?error") // 登录失败后的跳转url地址
.permitAll()
.and()
.exceptionHandling()
.accessDeniedPage("/401") // 拒接访问跳转页面
.and()
// 退出登录
.logout().permitAll()
.and()
// 访问路径URL的授权策略,如注册、登录免登录认证等
.authorizeRequests()
.antMatchers("/", "/home", "/register", "/auth/login").permitAll() //指定url放行
.antMatchers("/user
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
registry.addViewController("/home").setViewName("home");
registry.addViewController("/auth/login").setViewName("login");
registry.addViewController("/401").setViewName("401");
}
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // 允许跨域访问的路径
.allowedOriginPatterns("*") // 允许跨域访问的源
.allowedMethods("*") // 允许请求方法
.allowedHeaders("*") // 允许头部设置
.maxAge(168000) // 预检间隔时间
.allowCredentials(true); // 是否发送cookie
}
}
接口
package com.yzm.security02.controller;
import com.alibaba.fastjson.JSONObject;
import com.yzm.common.entity.HttpResult;
import com.yzm.security02.entity.User;
import com.yzm.security02.service.UserService;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
public class HomeController {
private final UserService userService;
private final PasswordEncoder passwordEncoder;
public HomeController(UserService userService, PasswordEncoder passwordEncoder) {
this.userService = userService;
this.passwordEncoder = passwordEncoder;
}
@PostMapping("register")
@ResponseBody
public Object register(@RequestParam String username, @RequestParam String password) {
User user = new User();
user.setUsername(username);
// 密码加密
user.setPassword(passwordEncoder.encode(password));
userService.save(user);
return HttpResult.ok("注册成功");
}
@GetMapping("/info")
@ResponseBody
public void info(HttpServletResponse response, @AuthenticationPrincipal UserDetails userDetails) throws IOException {
HttpUtils.successWrite(response, userDetails);
}
}
package com.yzm.security02.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/admin")
public class AdminController {
@GetMapping("/select")
public Object select() {
return "Select";
}
@GetMapping("/create")
public Object create() {
return "Create";
}
@GetMapping("/update")
public Object update() {
return "Update";
}
@GetMapping("/delete")
public Object delete() {
return "Delete";
}
}
package com.yzm.security02.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/user")
public class UserController {
@GetMapping("/select")
public Object select() {
return "Select";
}
@GetMapping("/create")
public Object create() {
return "Create";
}
@GetMapping("/update")
public Object update() {
return "Update";
}
@GetMapping("/delete")
public Object delete() {
return "Delete";
}
}
页面
home.html
首页
注册
用户详情
user
admin
login.html
登录页
You have been logged out.
You username or password is wrong
用户名密码登录
401.html
401
抱歉,你无权访问
7 测试
启动项目,注册admin、yzm
修改数据库,使用yzm的角色是2,admin是1,2双重身份,角色和权限就手动新增了
具体可以参考 上面的3 用户数据
数据修改完成后,返回主页,点击用户详情,跳转到我们自定义的登录页,默认是/login
.loginPage("/auth/login") //指定登录页的路径,默认/login
登录yzm,登录失败跳转
.failureUrl("/auth/login?error") // 登录失败后的跳转url地址
登录成功跳转
.defaultSuccessUrl("/home", true) // 登录成功后的跳转url地址
我们给yzm赋予的角色是USER
而设置url的也是USER
.authorizeRequests()
.antMatchers("/", "/home", "/register", "/auth/login").permitAll() //指定url放行
.antMatchers("/user/**").hasAnyRole("ADMIN", "USER") // 需要角色
.antMatchers("/admin/**").hasRole("ADMIN") // 需要角色
.anyRequest().authenticated() //其他任何请求都需要身份认证
但我们访问不了这些接口
这是因为Security自动给我们加了前缀ROEL_
.antMatchers("/user/**").hasAnyRole("ADMIN", "USER") // 需要角色
private static String hasAnyRole(String... authorities) {
String anyAuthorities = StringUtils.arrayToDelimitedString(authorities, "','ROLE_");
return "hasAnyRole('ROLE_" + anyAuthorities + "')";
}
修改数据库,如下
重启后就可以正常访问了 /user/** 接口
访问 /admin/** 还是无权,跳转到401
.exceptionHandling()
.accessDeniedPage("/401") // 拒接访问跳转页面
.and()
hasRole(hasAnyRole) 和 hasAuthority(hasAnyAuthority) 的 区别
hasRole:会自动添加前缀,hasRole(“ADMIN”) --> ROLE_ADMIN,即数据得存ROLE_ADMIN
hasAuthority:不会添加前缀,hasAuthority(“ADMIN”) 则数据库得存ADMIN
.antMatchers("/user/**").hasAnyRole("ADMIN", "USER")
.antMatchers("/admin/**").hasRole("ADMIN")
// 把hasRole换成hasAuthority
.antMatchers("/user/**").hasAnyAuthority("ROLE_ADMIN", "ROLE_USER")
.antMatchers("/admin/**").hasAuthority("ROLE_ADMIN")



