一,学习思路
1,在项目中引入thymeleaf、security,mybatisplus,感受security的自带form表单登录
2,添加自定义登录页面登录;
3,根据自定义页面登录成功失败进行页面跳转;
4,加入token授权验证,去掉session,不同角色登录成功后可请求不同接口
二,实践
1.1,pom文件中引入thymeleaf、security,mybatisplus包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.fasterxml.uuid</groupId>
<artifactId>java-uuid-generator</artifactId>
<version>4.0</version>
</dependency>
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
<version>2.0.33</version>
</dependency>
配置文件为yml格式:application.yml
server:
port: 9000
spring:
application:
name: security-test
# html存放的具体路径,可进行自定义,示例:resources/templates
# thymeleaf:
# cache: false
# prefix: classpath:/templates/
# encoding: utf-8
# suffix: .html
# servlet:
# content-type: text/html
datasource:
url: jdbc:mysql://ip:3306/test?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf8
username: root
password: 1111
driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
mapper-locations: classpath:mapper/*.xml
1.2,创建mysql数据库表 admin_user:(此处密码都是admin,下方会交大家如何生成加密串)
CREATE TABLE `admin_user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`password` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `ycyt-test`.`admin_user`(`id`, `user_name`, `password`) VALUES (1, 'admin', '$2a$10$cstNvzLTv0aHOQiRm4dEl.qvS9Wn53msyhJg2d2jxWQpvIaZdgi.e');
INSERT INTO `ycyt-test`.`admin_user`(`id`, `user_name`, `password`) VALUES (2, 'zhangsan', '$2a$10$cstNvzLTv0aHOQiRm4dEl.qvS9Wn53msyhJg2d2jxWQpvIaZdgi.e');
INSERT INTO `ycyt-test`.`admin_user`(`id`, `user_name`, `password`) VALUES (3, 'lisi', '$2a$10$cstNvzLTv0aHOQiRm4dEl.qvS9Wn53msyhJg2d2jxWQpvIaZdgi.e');
1.3, java代码:
/**
* 当前类实现UserDetailsService,必须实现
* 此处是处理username查询到的实体对象封装到UserDetails 中用于校验密码、权限的
*/
@Service
public class MyUserDetailsServiceImp implements UserDetailsService {
@Autowired
AdminUserMapper adminUserMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AdminUser adminUser = adminUserMapper.selectOne(new LambdaQueryWrapper<AdminUser>()
.eq(AdminUser::getUserName, username));
if (adminUser == null) {
throw new UsernameNotFoundException("当前用户不存在!");
}
List<String> roles = new ArrayList<>();
UserDetails userDetails = null;
roles.add("admin");
userDetails = User.builder().username(username)
.password(adminUser.getPassword())
.authorities(roles.toArray(new String[roles.size()]))
.build();
return userDetails;
}
}
/**
* security的配置文件,必须加
*
*/
@Configuration
@EnableWebSecurity
public class MySecurityConfig {
@Autowired
MyUserDetailsServiceImp userDetailsService;
/**
* 配置过滤器链
* @param http
* @return
* @throws Exception
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.formLogin();//此处使用security的登录页面进行登录
// 自定义从数据库查询用户信息
http.userDetailsService(userDetailsService);
// 请求访问策略
http.authorizeRequests()
.antMatchers("/login").permitAll() //放行资源,自定义添加
.antMatchers("/adminTest").permitAll()//静态页面此处不写登录成功后会报错无法访问当前页面
.anyRequest().authenticated()//除以上放行资源外,其他全部拦截
;
//禁用csrf,前后端分离需要配置
http.csrf().disable();
return http.build();
}
/**
* 配置加密方法 - 否则会报错There is no PasswordEncoder mapped for the id “null”
* @return
*/
@Bean
public BCryptPasswordEncoder encoding(){
return new BCryptPasswordEncoder();
}
}
/**
* Controller测试
* @date 2023/6/25 14:57
*/
@Controller
@Slf4j
public class TestController {
@GetMapping("/admin/test")
public String adminTest() {
log.info("----------/admin/test");
return "adminTest";
}
}
注意:可根据此处进行生成密码
密码可以由 new BCryptPasswordEncoder() 对象调用encode方法得到
public static void main(String[] args) {
BCryptPasswordEncoder bcrpt = new BCryptPasswordEncoder();
System.out.println(bcrpt.encode("admin"));
}
注意:由于此处使用了mybatis,必须要在启动类配置扫描注解 @MapperScan("包名") //此处包名可以是项目包名例如:com.xxx.xxx
此处实体类、mapper、xml文件不再贴出,请自行生成。
1.4, html页面 (在resource/templates下创建)adminTest
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
这是---------------------adminTest页面------------------------------
</body>
</html>
1.5, 启动测试
访问 /admin/test 会被拦截 进入 /login 此时登录
账号密码登录成功后
--------------------------------------------------分隔-----------------------------------
2.1 修改MySecurityConfig类为
修改内容
http.formLogin()
.loginProcessingUrl("/login")
.loginPage("/showLogin")
.usernameParameter("userName");
http.authorizeRequests()
.antMatchers("/login","/showLogin").permitAll()
修改后:
/**
* security的配置文件,必须加
*
*/
@Configuration
@EnableWebSecurity
public class MySecurityConfig {
@Autowired
MyUserDetailsServiceImp userDetailsService;
/**
* 配置过滤器链
* @param http
* @return
* @throws Exception
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
//此处使用自定义的login登录页面进行登录
http.formLogin()
.loginProcessingUrl("/login")
.loginPage("/showLogin")
.usernameParameter("userName");//此处是为了对应login.html中的userName
// 自定义从数据库查询用户信息
http.userDetailsService(userDetailsService);
// 请求访问策略
http.authorizeRequests()
.antMatchers("/login","/showLogin").permitAll() //放行资源,自定义添加
.antMatchers("/adminTest").permitAll()//静态页面此处不写登录成功后会报错无法访问当前页面
.anyRequest().authenticated()//除以上放行资源外,其他全部拦截
;
//禁用csrf,前后端分离需要配置
http.csrf().disable();
return http.build();
}
/**
* 配置加密方法 - 否则会报错There is no PasswordEncoder mapped for the id “null”
* @return
*/
@Bean
public BCryptPasswordEncoder encoding(){
return new BCryptPasswordEncoder();
}
}
2.2,修改Controller为
/**
* Controller测试
* @date 2023/6/25 14:57
*/
@Controller
@Slf4j
public class TestController {
@GetMapping("/admin/test")
public String adminTest() {
log.info("----------/admin/test");
return "adminTest";
}
@RequestMapping("/showLogin")
public String showLogin(HttpSession session, Model model, AdminUser adminUser, HttpServletRequest request) {
return "login";
}
}
2.3,增加login.html(路径同上一个html方一致)
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<form method="post" action="/login">
账号:<input type="username" name="userName"><br/>
密码:<input type="password" name="password"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
2.4,启动测试
仍然请求 /admin/test ,此时会跳转到 /showLogin 进入login.html页面
然后登录,成功后会跳转 /admin/test 接口对应的页面。
3.1 登录成功后我想让跳转到固定的成功页面 /index 失败的时候 跳转 /login 并提示
login.html 更新为:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.w3.org/1999/xhtml">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.show-error-msg{
color: red;
}
</style>
</head>
<body>
<form method="post" action="/login">
<p class="show-error-msg" th:text="${msg}"></p>
账号:<input type="username" name="userName"><br/>
密码:<input type="password" name="password"><br/>
<input type="submit" value="提交">
</form>
</body>
</html>
<p class="show-error-msg" th:text="${msg}"></p>此处是thymeleaf架构,接口返回的Model中的msg属性的展示
TestController:更新为
/**
* Controller测试
* @date 2023/6/25 14:57
*/
@Controller
@Slf4j
public class TestController {
@GetMapping("/admin/test")
public String adminTest() {
log.info("----------/admin/test");
return "adminTest";
}
@RequestMapping("/showLogin")
public String showLogin(HttpSession session, Model model, AdminUser adminUser, HttpServletRequest request) {
String error = request.getParameter("error");
log.info("打印requestURI:"+ error);
if (!StringUtils.isEmpty(error)){
model.addAttribute("msg","用户名或密码错误");
}
return "login";
}
//登录成功跳转
@GetMapping("/")
public String index() {
return "index";
}
//最后验证token以及权限时使用
@GetMapping("/sysUser/test")
public String userTest() {
log.info("----------/sysUser/test");
return "userTest";
}
//最后验证token以及权限时使用
@GetMapping("/user/test")
public String visitorTest() {
log.info("----------/user/test");
return "visitorTest";
}
}
model.addAttribute("msg","用户名或密码错误");此处返回属性展示在页面
SecurityConfig:
http.formLogin()
.loginProcessingUrl("/login")
//加上后面内容即可
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
response.sendRedirect("/");
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.sendRedirect("/showLogin?error=true");
}
})
仍然请求 /admin/test ,此时会跳转到 /showLogin 进入login.html页面,错误,成功则会进入index页面
错误效果:
4.1,创建数据库表:
admin_menu : 菜单表
admin_menu_role : 菜单角色表 (对应菜单哪些角色可以访问)
admin_role: 角色表
admin_user_role:用户角色表 (用户具有哪些角色)
CREATE TABLE `admin_menu` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`pid` int(11) DEFAULT NULL,
`auth_name` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`url_path` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `ycyt-test`.`admin_menu`(`id`, `pid`, `auth_name`, `url_path`) VALUES (1, NULL, '管理员页面', '/admin/test');
INSERT INTO `ycyt-test`.`admin_menu`(`id`, `pid`, `auth_name`, `url_path`) VALUES (2, NULL, 'vip用户页面', '/sysUser/test');
INSERT INTO `ycyt-test`.`admin_menu`(`id`, `pid`, `auth_name`, `url_path`) VALUES (3, NULL, '普通页面', '/user/test');
INSERT INTO `ycyt-test`.`admin_menu`(`id`, `pid`, `auth_name`, `url_path`) VALUES (4, NULL, '所有', '/');
INSERT INTO `ycyt-test`.`admin_menu`(`id`, `pid`, `auth_name`, `url_path`) VALUES (5, NULL, '所有', '/showLogin');
CREATE TABLE `admin_menu_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`menu_id` int(11) DEFAULT NULL,
`role_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `ycyt-test`.`admin_menu_role`(`id`, `menu_id`, `role_id`) VALUES (1, 1, 1);
INSERT INTO `ycyt-test`.`admin_menu_role`(`id`, `menu_id`, `role_id`) VALUES (2, 2, 1);
INSERT INTO `ycyt-test`.`admin_menu_role`(`id`, `menu_id`, `role_id`) VALUES (3, 3, 1);
INSERT INTO `ycyt-test`.`admin_menu_role`(`id`, `menu_id`, `role_id`) VALUES (4, 2, 2);
INSERT INTO `ycyt-test`.`admin_menu_role`(`id`, `menu_id`, `role_id`) VALUES (5, 3, 2);
INSERT INTO `ycyt-test`.`admin_menu_role`(`id`, `menu_id`, `role_id`) VALUES (6, 3, 3);
INSERT INTO `ycyt-test`.`admin_menu_role`(`id`, `menu_id`, `role_id`) VALUES (7, 4, 1);
INSERT INTO `ycyt-test`.`admin_menu_role`(`id`, `menu_id`, `role_id`) VALUES (8, 4, 2);
INSERT INTO `ycyt-test`.`admin_menu_role`(`id`, `menu_id`, `role_id`) VALUES (9, 4, 3);
INSERT INTO `ycyt-test`.`admin_menu_role`(`id`, `menu_id`, `role_id`) VALUES (10, 5, 1);
INSERT INTO `ycyt-test`.`admin_menu_role`(`id`, `menu_id`, `role_id`) VALUES (11, 5, 2);
INSERT INTO `ycyt-test`.`admin_menu_role`(`id`, `menu_id`, `role_id`) VALUES (12, 5, 3);
CREATE TABLE `admin_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`role_name` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`role_description` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `ycyt-test`.`admin_role`(`id`, `role_name`, `role_description`) VALUES (1, 'admin', '管理员');
INSERT INTO `ycyt-test`.`admin_role`(`id`, `role_name`, `role_description`) VALUES (2, 'sysUser', '系统用户');
INSERT INTO `ycyt-test`.`admin_role`(`id`, `role_name`, `role_description`) VALUES (3, 'user', '普通用户');
CREATE TABLE `admin_user_role` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) DEFAULT NULL,
`role_id` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO `ycyt-test`.`admin_user_role`(`id`, `user_id`, `role_id`) VALUES (1, 1, 1);
INSERT INTO `ycyt-test`.`admin_user_role`(`id`, `user_id`, `role_id`) VALUES (2, 2, 2);
INSERT INTO `ycyt-test`.`admin_user_role`(`id`, `user_id`, `role_id`) VALUES (3, 3, 3);
4.2,代码修改
findRoleByUserId 下方使用方法对应sql;#{userId}:变量
select ur.user_id as userId, role_id as roleId, r.role_name as roleName
from admin_user_role ur
inner join admin_role r on r.id = ur.role_id
where ur.user_id = #{userId}
/**
* 当前类实现UserDetailsService,必须实现
* 此处是处理username查询到的实体对象封装到UserDetails 中用于校验密码、权限的
*/
@Service
public class MyUserDetailsServiceImp implements UserDetailsService {
@Autowired
AdminUserMapper adminUserMapper;
//此处注意,对应实体和mapper要先生成,这里不再赘述
@Autowired
AdminRoleMapper roleMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
AdminUser adminUser = adminUserMapper.selectOne(new LambdaQueryWrapper<AdminUser>()
.eq(AdminUser::getUserName, username));
if (adminUser == null) {
throw new UsernameNotFoundException("当前用户不存在!");
}
List<String> roles = new ArrayList<>();
UserDetails userDetails = null;
List<String> roles = new ArrayList<>();
List<UserRoleVo> userRoleVos = roleMapper.findRoleByUserId(adminUser.getId());
if (!userRoleVos.isEmpty()) {
userRoleVos.forEach(it -> roles.add(it.getRoleName()));
}
userDetails = User.builder().username(username)
.password(adminUser.getPassword())
.authorities(roles.toArray(new String[roles.size()]))
.build();
return userDetails;
}
}
//新增类
//获取当前路径需要的角色
@Component
@Slf4j
public class MySecurityMetaSource implements FilterInvocationSecurityMetadataSource {
@Autowired
private AdminMenuMapper adminMenuMapper;
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
log.info("MySecurityMetaSource - 自定义权限元数据开启");
//1.当前请求对象
String requestURI = ((FilterInvocation) object).getRequest().getRequestURI();
log.info("MySecurityMetaSource - 当前访问路径:{}", requestURI);
// 默认登录接口放行,url转化为小写且末尾包含login放行
if (requestURI.toLowerCase().endsWith("login")) {
log.info("MySecurityMetaSource - 白名单放行,不校验请求权限");
return null;
}
//2.查询所有权限和角色
List<MenuRoleVo> allRoleList = adminMenuMapper.findAllRole();
for (MenuRoleVo vo : allRoleList) {
if (antPathMatcher.match(vo.getUrlPath(), requestURI)) {
String[] roles = vo.getRoleName().split(",");
return SecurityConfig.createList(roles);
}
}
/*
* 如果返回null,就不会进行下一步的角色对比,所有用户都可以访问
* 如果需要对未分配角色的路由进行拦截,就像下面写法一样默认分配一个ROLE_UNKNOWN(名称自定义,不能与现有角色重名)角色,
* 后面对用户角色进行匹配时就会提示无权访问
*/
// return null;
return SecurityConfig.createList("ROLE_UNKNOWN");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return false;
}
}
//新增权限对比
@Component
@Slf4j
public class MyAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
throws AccessDeniedException, InsufficientAuthenticationException {
log.info("MyAccessDecisionManager - 自定义权限判断开启");
// 1,取出当前路由分配的所有角色
List<String> menuRoles = configAttributes.stream().map(e -> e.getAttribute()).collect(Collectors.toList());
log.info("MyAccessDecisionManager - 当前访问路径授权角色:{}", menuRoles);
// 2,判断当前路由是否未分配角色
if (menuRoles.contains("ROLE_UNKNOWN")) {
throw new AccessDeniedException("权限未开放,请联系管理员!");
}
// 3,用户角色和路由角色比对
for (GrantedAuthority grantedAuthority : authentication.getAuthorities()){
if (menuRoles.contains(grantedAuthority.getAuthority())) {
return;
}
}
throw new AccessDeniedException("权限不足,请联系管理员!");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return false;
}
@Override
public boolean supports(Class<?> clazz) {
return false;
}
}
//使用内存存储token
@Component
public class TokenAndAuthentication {
//线程安全的map存储
public static ConcurrentMap<String, Authentication> map = new ConcurrentHashMap<>();
public static Authentication getAuthenticationToken(String token){
return map.get(token);
}
public static void setAuthenticationToken(String token,Authentication authentication){
map.put(token,authentication);
}
//后期做登出时处理
public static void remove(String token){
map.remove(token);
}
}
//新增类token过滤器
@Component
@Slf4j
public class AuthorizationTokenFilter extends OncePerRequestFilter {
@Value("${server.port}")
private String port;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
log.info("AuthorizationTokenFilter - 自定义Token认证过滤器开启!");
String baseUrl = "http://localhost:" + port + "/";
// 默认登陆接口放行
String url = request.getRequestURI();
if (url.toLowerCase().endsWith("login") || baseUrl.equals(url)) {
log.info("AuthorizationTokenFilter - 白名单放行,不校验token");
filterChain.doFilter(request, response);
return;
}
// 获取token
String token = request.getHeader("Authorization");
if (!StringUtils.hasLength(token)) {
// 返回前端提示 需要携带Token
returnMsg(response, ResponseApi.authenticationDoesNot());
return;
}
// 存在token则从内存中取出用户信息
Authentication authentication = TokenAndAuthentication.getAuthenticationToken(token);
if (authentication != null) {
// 添加至上下文中
SecurityContextHolder.getContext().setAuthentication(authentication);
filterChain.doFilter(request, response);
} else {
// 返回前端提示需要重新登陆
returnMsg(response, ResponseApi.authenticationNoLogin("当前未登录,请登录!"));
}
}
/**
* Token认证失败返回前端提示
*
* @param response
* @param error
* @throws IOException
*/
private void returnMsg(HttpServletResponse response, ResponseApi error) throws IOException {
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(JSON.toJSONString(error));
out.flush();
out.close();
}
}
//MySecurityConfig修改为
@Configuration
@EnableWebSecurity
public class MySecurityConfig {
@Autowired
MyUserDetailsServiceImp userDetailsService;
@Autowired
MySecurityMetaSource mySecurityMetaSource;
@Autowired
MyAccessDecisionManager myAccessDecisionManager;
@Autowired
MyAccessDeniedHandler myAccessDeniedHandler;
@Autowired
AuthorizationTokenFilter tokenFilter;
/**
* 配置过滤器链
* @param http
* @return
* @throws Exception
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// 启用Security登录
http.formLogin()
.loginProcessingUrl("/login")//登录处理路径
.loginPage("/showLogin")
.successHandler(new AuthenticationSuccessHandler() {
@Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
String token = Generators.randomBasedGenerator().generate().toString();
TokenAndAuthentication.setAuthenticationToken(token, authentication);
ResponseApi<Object> result = ResponseApi.ok(token);
response.setContentType("application/json;charset=utf-8");
PrintWriter out = response.getWriter();
out.write(JSON.toJSONString(result));
out.flush();
out.close();
//当前方法中上述代码,生成token,添加进入TokenAndAuthentication对应的内存中,返回前端页面打印token信息
//response.sendRedirect("/");
}
})
.failureHandler(new AuthenticationFailureHandler() {
@Override
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {
response.sendRedirect("/showLogin?error=true");
}
})
.usernameParameter("userName")
;
// 自定义从数据库查询用户信息
http.userDetailsService(userDetailsService);
// 请求访问策略
http.authorizeRequests()
.antMatchers("/login","/showLogin","/testLogin").permitAll() //放行资源,自定义添加
.antMatchers("/adminTest","/userTest","/visitorTest").permitAll()
.anyRequest().authenticated()//除以上放行资源外,其他全部拦截
//此方法加入权限验证
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
//自定义角色权限获取
object.setSecurityMetadataSource(mySecurityMetaSource);
//权限角色对比
object.setAccessDecisionManager(myAccessDecisionManager);
object.setRejectPublicInvocations(false);
return object;
}
})
;
http.addFilterBefore(tokenFilter, UsernamePasswordAuthenticationFilter.class);
//权限不足异常返回
http.exceptionHandling()
.accessDeniedHandler(myAccessDeniedHandler);
//禁用csrf,前后端分离需要配置
http.csrf().disable()
//此处禁用session
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
return http.build();
}
/**
* 配置加密方法 - 否则会报错There is no PasswordEncoder mapped for the id “null”
* @return
*/
@Bean
public BCryptPasswordEncoder encoding(){
return new BCryptPasswordEncoder();
}
}
@Data
public class ResponseApi<T> {
public Integer code;
public String msg;
public T data;
public ResponseApi(Integer code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public static ResponseApi<Object> ok() {
return new ResponseApi(200, "ok", null);
}
public static ResponseApi<Object> ok(Object data) {
return new ResponseApi(200, "ok", data);
}
public static ResponseApi fail(Integer code) {
return new ResponseApi(code, "failed", null);
}
public static ResponseApi fail(Integer code, String msg) {
return new ResponseApi(code, msg, null);
}
public static ResponseApi fail(Integer code, String msg, Object data) {
return new ResponseApi(code, msg, data);
}
public static ResponseApi authenticationDoesNot() {
return new ResponseApi(9999, "需要携带token", null);
}
public static ResponseApi authenticationNoLogin(String msg) {
return new ResponseApi(403, msg, null);
}
}
重启服务
此处我们请求接口要注意,此时已经需要 验证token;所以我们要请求跳转登录页的接口 /showLogin 登录成功后
复制data对应字符串放入对应位置,如下方截图所示:
然后使用postman, 请求 /admin/test (当前用户为zhangsan,此接口无权限)
/sysUser/tset
/user/test
验证成功!