Springboot2.7.13版本+thymeleaf+security整合

一,学习思路

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

验证成功! 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江湖是人情世故

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值