现在大部分应用前后端分离鲜明的,前端不要接触过多的业务逻辑,都由后端解决,基本思路是这样的:服务端通过 JSON字符串,告诉前端用户有没有登录、认证,前端根据这些提示跳转对应的登录页、认证页等。
在学习springboot过程中,研究了下spring security使用JWT代替Session的方案,并适配了前后端分离时的ajax请求的Json返回。
此代码有很多不足,用到生产环境,需要做一些改动,如springsecurity中授权的配置是硬代码,用户权限修改后的刷新,Token在分布式环境下的存储等。

1、向Mysql导入基本使用的表结构

DROP TABLE IF EXISTS `sys_permission`;

CREATE TABLE `sys_permission` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT '',
  `descritpion` varchar(10) DEFAULT '',
  `url` varchar(10) DEFAULT '',
  `pid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4;

/*Data for the table `sys_permission` */

insert  into `sys_permission`(`id`,`name`,`descritpion`,`url`,`pid`) values (3,'ADMIN','','',NULL),(4,'USER','','',NULL);

/*Table structure for table `sys_permission_role` */

DROP TABLE IF EXISTS `sys_permission_role`;

CREATE TABLE `sys_permission_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) DEFAULT NULL,
  `permission_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

/*Data for the table `sys_permission_role` */

insert  into `sys_permission_role`(`id`,`role_id`,`permission_id`) values (1,2,4),(2,1,3);

/*Table structure for table `sys_role` */

DROP TABLE IF EXISTS `sys_role`;

CREATE TABLE `sys_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT '',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

/*Data for the table `sys_role` */

insert  into `sys_role`(`id`,`name`) values (1,'ADMIN'),(2,'USER');

/*Table structure for table `sys_role_user` */

DROP TABLE IF EXISTS `sys_role_user`;

CREATE TABLE `sys_role_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `Sys_user_id` int(11) DEFAULT NULL,
  `sys_role_id` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

/*Data for the table `sys_role_user` */

insert  into `sys_role_user`(`id`,`Sys_user_id`,`sys_role_id`) values (1,2,1),(2,3,2);

/*Table structure for table `sys_user` */

DROP TABLE IF EXISTS `sys_user`;

CREATE TABLE `sys_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(10) DEFAULT NULL,
  `password` varchar(100) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4;

/*Data for the table `sys_user` */

insert  into `sys_user`(`id`,`name`,`password`) values (2,'admin','$2a$10$Yks2LoqzBUHEWjyLCnsdtepI4oCNip9yNdf67y19ewF8geORNAO5m');

2、引入依赖
这里使用的gradle构建,使用maven时将依赖替换为maven的就好
使用springboot的版本为2.4.3,经测试默认的tomcat使用过程中控制台会一直报错,使用undertow做了替换。springboot的插件配置略。

configurations {
    compile.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-tomcat'
    compile.exclude group: 'org.apache.tomcat'
    compile.exclude group: 'org.apache.tomcat.embed'
}
dependencies {
    compile "org.springframework.boot:spring-boot-starter-security"
    compile group: 'mysql', name: 'mysql-connector-java', version: "8.0.15"
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-data-jpa'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-web'
    compile group: 'org.springframework.boot', name: 'spring-boot-starter-undertow'
    compile 'javax:javaee-api:7.0'
    compile group: 'com.auth0', name: 'java-jwt', version: "3.3.0"
}

3、几个实体和Dao
这里用了JPA访问数据库
使用了lombok插件,自动生成get/set,依赖里没有引入是因为在我的父模块中已经引入过了

package com.iscas.biz.security.domain;


import javax.persistence.*;

@Entity
@Table(name = "sys_permission")
@Data
public class Permission {
 
    @Id
    @Column(name="id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;
 
    //权限名称
    @Column(name="name")
    private String name;
 
    //权限描述
    @Column(name = "descritpion")
    private String descritpion;
 
    //授权链接
    @Column(name = "url")
    private String url;
 
    //父节点id
    @Column(name = "pid")
    private int pid;

}
package com.iscas.biz.security.domain;

import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface PermissionRepository extends CrudRepository<Permission,Integer> {
 
}
package com.iscas.biz.security.domain;

import javax.persistence.*;

@Entity
@Table(name="sys_role")
@Data
public class SysRole {
 
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
 
    @Column(name = "name")
    private String name;
}
package com.iscas.biz.security.domain;

import org.springframework.data.repository.CrudRepository;

public interface SysRoleRepository extends CrudRepository<SysRole,Integer> {
}
package com.iscas.biz.security.domain;

import javax.persistence.*;
import java.util.List;

@Entity
@Table(name = "sys_user")
@Data
public class SysUser {
 
    @Id
    @Column(name="id")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    @Column(name = "name")
    private String name;
    @Column(name = "password")
    private String password;
 
    @Transient
    private List<SysRole> roles;
     
    @Override
    public String toString() {
        return "SysUser{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                ", roles=" + roles +
                '}';
    }
}
package com.iscas.biz.security.domain;

import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;

@Repository
public interface SysUserRepository extends CrudRepository<SysUser,Integer> {
 
    @Query("select a from SysUser a where a.name=:name")
    public SysUser getUserByName(@Param("name") String name);
}

4、几个访问数据库的service

package com.iscas.biz.security.service;

import com.iscas.biz.security.domain.Permission;

import java.util.List;

public interface PermissionService {
    public List<Permission> findAll();
    public List<Permission> findByAdminUserId(int userId);
}
package com.iscas.biz.security.service;

import com.iscas.biz.security.domain.Permission;
import com.iscas.biz.security.domain.PermissionRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import java.util.ArrayList;
import java.util.List;

/**
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2021/2/27 14:54
 * @since jdk1.8
 */
@Service
public class PermissionServiceImpl implements PermissionService {

    @Autowired
    PermissionRepository permissionRepository;

    @PersistenceContext
    private EntityManager entityManager;

    @Override
    public List<Permission> findAll() {
        return null;
    }

    @Override
    public List<Permission> findByAdminUserId(int userId) {

        List<Permission> list = new ArrayList<Permission>();
        List<Object[]> abcs = entityManager.createNativeQuery("select p.* \n" +
                "        from Sys_User u\n" +
                "        LEFT JOIN sys_role_user sru on u.id= sru.Sys_User_id\n" +
                "        LEFT JOIN Sys_Role r on sru.Sys_Role_id=r.id\n" +
                "        LEFT JOIN Sys_permission_role spr on spr.role_id=r.id\n" +
                "        LEFT JOIN Sys_permission p on p.id =spr.permission_id\n" +
                "        where u.id=" + userId).getResultList();
        for (Object[] abc : abcs) {
            Permission permission = new Permission();
            permission.setId(Integer.valueOf(abc[0] + ""));
            permission.setName(abc[1] + "");
            permission.setDescritpion(abc[2] + "");
            permission.setUrl(abc[3] + "");
//            permission.setPid(Integer.valueOf(abc[4]+""));
            list.add(permission);
        }
        return list;
    }
}

5、自定义springsecurtiy的UserdetailSerivice

package com.iscas.biz.security.service;

import com.iscas.biz.security.domain.Permission;
import com.iscas.biz.security.domain.SysUser;
import com.iscas.biz.security.domain.SysUserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
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 javax.transaction.Transactional;
import java.util.ArrayList;
import java.util.List;

//自定义userdetailservice
@Service
public class UserDetailServiceImpl implements UserDetailsService {
 
    @Autowired
    SysUserRepository sysUserRepository;
    @Autowired
    PermissionService permissionService;
//    @Autowired
//    PasswordEncoder passwordEncoder;
 
    @Transactional
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        SysUser sysUser = sysUserRepository.getUserByName(username);
        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
        if (sysUser != null) {
            System.err.println("sysUser===============" + sysUser);
            //获取用户的授权
            List<Permission> permissions = permissionService.findByAdminUserId(sysUser.getId());
            //声明授权文件
            for (Permission permission : permissions) {
                if (permission != null && permission.getName() != null) {
                    GrantedAuthority grantedAuthority = new SimpleGrantedAuthority("ROLE_"+permission.getName());
                    grantedAuthorities.add(grantedAuthority);
                }
            }
        }
        System.err.println("grantedAuthorities===============" + grantedAuthorities);
        return new User(sysUser.getName(), sysUser.getPassword(), grantedAuthorities);
    }
}

6、自定义授权失败处理,返回JSON,而不是页面

package com.iscas.biz.security.handler;

import com.iscas.biz.security.util.OutputUtils;
import com.iscas.common.web.tools.json.JsonUtils;
import com.iscas.templet.common.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 *
 * 授权失败处理
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2021/03/14 15:09
 * @since jdk1.8
 */

@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        ResponseEntity responseEntity = new ResponseEntity();
        responseEntity.setMessage("没有权限");
        OutputUtils.output(httpServletResponse, 403, responseEntity);
    }
}

7、自定义未登录处理,返回JSON,而不是页面

package com.iscas.biz.security.handler;

import com.iscas.biz.security.util.OutputUtils;
import com.iscas.common.web.tools.json.JsonUtils;
import com.iscas.templet.common.ResponseEntity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 *
 * 未登陆
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2021/03/14 14:30
 * @since jdk1.8
 */

@Component
public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        ResponseEntity responseEntity = new ResponseEntity();
        responseEntity.setMessage("未登录");
        OutputUtils.output(httpServletResponse, 401, responseEntity);
    }
}

8、自定义登录失败处理,返回JSON,而不是页面

package com.iscas.biz.security.handler;

import com.iscas.biz.security.util.OutputUtils;
import com.iscas.common.web.tools.json.JsonUtils;
import com.iscas.templet.common.ResponseEntity;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 *
 * 登陆失败处理
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2021/03/14 14:32
 * @since jdk1.8
 */

@Component
public class CustomAuthenticationFailureHandler implements AuthenticationFailureHandler {

    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        ResponseEntity responseEntity = new ResponseEntity();
        responseEntity.setMessage("登录失败");
        OutputUtils.output(httpServletResponse, 401, responseEntity);
    }
}

9、自定义登录成功处理,返回JSON,而不是页面

package com.iscas.biz.security.handler;

import com.iscas.biz.security.util.JWTUtils;
import com.iscas.biz.security.util.OutputUtils;
import com.iscas.templet.common.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 *
 * 登陆成功处理
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2021/03/14 14:33
 * @since jdk1.8
 */

@Component
public class CustomAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        ResponseEntity responseEntity = new ResponseEntity();
        responseEntity.setMessage("登录成功");

        UserDetails user = (UserDetails) authentication.getPrincipal();
		//生成JWT token
        String jwtToken = JWTUtils.createToken(user.getUsername(), 5);
        responseEntity.setValue(jwtToken);

        OutputUtils.output(httpServletResponse, 200, responseEntity);
    }
}

10、自定义登出处理,返回JSON,而不是页面

package com.iscas.biz.security.handler;

import com.iscas.biz.security.util.JWTUtils;
import com.iscas.biz.security.util.OutputUtils;
import com.iscas.common.web.tools.json.JsonUtils;
import com.iscas.templet.common.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 *
 * 登出处理
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2021/03/14 15:16
 * @since jdk1.8
 */

@Component
public class CustomLogoutSuccessHandler implements LogoutSuccessHandler {

    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        ResponseEntity responseEntity = new ResponseEntity();
        String authorization = httpServletRequest.getHeader("Authorization");
        JWTUtils.TOKENS.remove(authorization);
        responseEntity.setMessage("登出成功");
        OutputUtils.output(httpServletResponse, 200, responseEntity);
    }
}

11、自定义token校验过滤器

package com.iscas.biz.security.filter;

import com.auth0.jwt.interfaces.Claim;
import com.iscas.biz.security.util.JWTUtils;
import com.iscas.biz.security.util.OutputUtils;
import com.iscas.templet.common.ResponseEntity;
import com.iscas.templet.exception.ValidTokenException;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;


@SuppressWarnings("ALL")
//@Component
public class JwtAuthenticationTokenFilter extends BasicAuthenticationFilter {

//    @Autowired
    private UserDetailsService userDetailsService;
    public void setUserDetailsService(UserDetailsService userDetailsService) {
        this.userDetailsService = userDetailsService;
    }
//
    private AuthenticationManager authenticationManager;


    public JwtAuthenticationTokenFilter(AuthenticationManager authenticationManager) {

        super(authenticationManager);
        this.authenticationManager = authenticationManager;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
        String authHeader = request.getHeader("Authorization");
        if (authHeader == null) {
            //如果没有传递Authorization,传递给下一个过滤器处理
            doFilter(request, response, chain);
            return;
        }
        try {
            if (!JWTUtils.TOKENS.contains(authHeader)) {
                throw new ValidTokenException("token已被删除");
            }
            Map<String, Claim> stringClaimMap = JWTUtils.verifyToken(authHeader);
            String username = stringClaimMap.get("username").asString();
            if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);

                if (userDetails != null) {
                    UsernamePasswordAuthenticationToken authentication =
                            new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }

        } catch (ValidTokenException e) {
//            throw new RuntimeException(e);
            ResponseEntity responseEntity = new ResponseEntity();
            responseEntity.setMessage("权限校验出错");
            responseEntity.setDesc(e.getMessage());
            OutputUtils.output(response, 403, responseEntity);
            return;
        }

        chain.doFilter(request, response);
    }
}

12、配置security

package com.iscas.biz.security.config;

import com.iscas.biz.security.filter.JwtAuthenticationTokenFilter;
import com.iscas.biz.security.handler.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

/**
 * 参考 :
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2021/2/27 14:35
 * @since jdk1.8
 */

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    private final UserDetailsService userDetailsService;

    //  未登陆时返回 JSON 格式的数据给前端(否则为 html)
    private final CustomAuthenticationEntryPoint authenticationEntryPoint;

    // 登录成功返回的 JSON 格式数据给前端(否则为 html)
    private final CustomAuthenticationSuccessHandler authenticationSuccessHandler;

    //  登录失败返回的 JSON 格式数据给前端(否则为 html)
    private final CustomAuthenticationFailureHandler authenticationFailureHandler;

    // 注销成功返回的 JSON 格式数据给前端(否则为 登录时的 html)
    private final CustomLogoutSuccessHandler logoutSuccessHandler;

    // 无权访问返回的 JSON 格式数据给前端(否则为 403 html 页面)
    private final CustomAccessDeniedHandler accessDeniedHandler;

    // 自定义安全认证
//    private final CustomAuthenticationProvider provider;

    // JWT 拦截器
//    private final JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;


    @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
    public WebSecurityConfig(UserDetailsService userDetailsService, CustomAuthenticationEntryPoint authenticationEntryPoint,
                             CustomAuthenticationSuccessHandler authenticationSuccessHandler, CustomAuthenticationFailureHandler authenticationFailureHandler, CustomLogoutSuccessHandler logoutSuccessHandler, CustomAccessDeniedHandler accessDeniedHandler /*, CustomAuthenticationProvider provider,*/ /*JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter*/) {
        this.userDetailsService = userDetailsService;
        this.authenticationEntryPoint = authenticationEntryPoint;
        this.authenticationSuccessHandler = authenticationSuccessHandler;
        this.authenticationFailureHandler = authenticationFailureHandler;
        this.logoutSuccessHandler = logoutSuccessHandler;
        this.accessDeniedHandler = accessDeniedHandler;
//        this.provider = provider;
//        this.jwtAuthenticationTokenFilter = jwtAuthenticationTokenFilter;
    }


    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter = new JwtAuthenticationTokenFilter(authenticationManager());
        jwtAuthenticationTokenFilter.setUserDetailsService(userDetailsService);

        http.csrf().disable()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) // 使用 JWT,关闭session
                .and()
                .httpBasic().authenticationEntryPoint(authenticationEntryPoint)

                .and()
                .authorizeRequests()
                .antMatchers("/decision/**","/govern/**").hasAnyRole("USER","ADMIN")//对decision和govern 下的接口 需要 USER 或者 ADMIN 权限
                .antMatchers("/admin/login").permitAll()///admin/login 不限定
                .antMatchers("/admin/**").hasRole("ADMIN")//对admin下的接口 需要ADMIN权限
                .antMatchers("/admin2/**").hasRole("AAA")//对admin2下的接口 需要AAA权限
                .antMatchers("/oauth/**").permitAll()//不拦截 oauth 开放的资源
                .antMatchers("/admin3/**").permitAll()//不拦截 admin3 开放的资源
//                .anyRequest()
//                .access("@rbacauthorityservice.hasPermission(request, authentication)")

                .and()
                .formLogin()  //开启登录
                .successHandler(authenticationSuccessHandler) // 登录成功
                .failureHandler(authenticationFailureHandler) // 登录失败
                .permitAll()

                .and()
                .logout()
                .logoutSuccessHandler(logoutSuccessHandler)
                .permitAll()

                .and()
                .authorizeRequests()
                .anyRequest().permitAll()//其他没有限定的请求,允许访问
//                .and().anonymous()//对于没有配置权限的其他请求允许匿名访问
//                ;
                .and()
                .addFilter(jwtAuthenticationTokenFilter); // JWT Filter

        // 记住我
        http.rememberMe().rememberMeParameter("remember-me")
                .userDetailsService(userDetailsService).tokenValiditySeconds(300);

        http.exceptionHandling().accessDeniedHandler(accessDeniedHandler); // 无权访问 JSON 格式的数据



    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
//        auth.authenticationProvider(provider);
        auth.userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoder());
    }

    @Override
    public void configure(WebSecurity web) throws Exception {
        super.configure(web);
    }
}

13、定义几个测试controller

package com.iscas.biz.security.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("admin")
public class MainController {
 
 
    @RequestMapping("/")
    public String index(){
 
        return "index" ;
    }
 
    @RequestMapping("/detail")
    public String hello(){
        return "hello" ;
    }
 
    @RequestMapping("/login")
    public String login() {
        return "login";
    }
}
package com.iscas.biz.security.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("admin2")
public class Main2Controller {
 
 
    @RequestMapping("/")
    public String index(){
 
        return "index" ;
    }
 
    @RequestMapping("/detail")
    public String hello(){
        return "hello" ;
    }
 
    @RequestMapping("/login")
    public String login() {
        return "login";
    }
}
package com.iscas.biz.security.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("admin3")
public class Main3Controller {
 
 
    @RequestMapping("/")
    public String index(){
 
        return "index" ;
    }
 
    @RequestMapping("/detail")
    public String hello(){
        return "hello" ;
    }
 
    @RequestMapping("/login")
    public String login() {
        return "login";
    }
}

14、用到的几个工具类

package com.iscas.biz.security.util;

import com.iscas.common.web.tools.json.JsonUtils;
import com.iscas.templet.common.ResponseEntity;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 输出工具类
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2018/7/17 17:33
 * @since jdk1.8
 */
public class OutputUtils {
    private OutputUtils(){}

    public static void output(HttpServletResponse response, int status, String msg, String desc) throws IOException {
        response.setContentType("application/json; charset=utf-8");
        response.setCharacterEncoding("UTF-8");
        response.setStatus(status);
        ServletOutputStream pw = response.getOutputStream();
        ResponseEntity responseEntity = new ResponseEntity(status,msg);
        responseEntity.setDesc(desc);
        pw.write(JsonUtils.toJson(responseEntity).getBytes("UTF-8"));
        pw.flush();
//        pw.close();
    }

    public static void output(HttpServletResponse response, int status, ResponseEntity responseEntity) throws IOException {
        response.setContentType("application/json; charset=utf-8");
        response.setCharacterEncoding("UTF-8");
        response.setStatus(status);
        ServletOutputStream pw = response.getOutputStream();
        pw.write(JsonUtils.toJson(responseEntity).getBytes("UTF-8"));
        pw.flush();
//        pw.close();
    }
}
package com.iscas.biz.security.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.iscas.templet.exception.ValidTokenException;

import java.io.UnsupportedEncodingException;
import java.util.*;

/**
 * JWT工具类
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2018/7/16 22:29
 * @since jdk1.8
 */
public class JWTUtils {
    public static Set<String> TOKENS = new HashSet<>();
    private JWTUtils(){}
    public static final String SECRET = "ISCAS";
    public static String createToken(String username, int expire) throws UnsupportedEncodingException {
        Date iatDate = new Date();
//        Calendar nowTime = Calendar.getInstance();
//        nowTime.add(Calendar.MINUTE,expire);
//        Date expiresDate = nowTime.getTime();
        Date expiresDate = DateRaiseUtils.afterOffsetDate(expire * 60 * 1000L);
        Map<String, Object> map = new HashMap<>(2 << 2);
        map.put("alg", "HS256");
        map.put("typ","JWT");
        String token = JWT.create()
                .withHeader(map)
                .withClaim("username", username)
                .withClaim("date", iatDate)
                .withExpiresAt(expiresDate)
                .withIssuedAt(iatDate)
                .sign(Algorithm.HMAC256(SECRET));
        TOKENS.add(token);
        //将token缓存起来
//        CaffCacheUtils.set(token, iatDate);
//        IAuthCacheService authCacheService = SpringService.getApplicationContext().getBean(IAuthCacheService.class);
//        authCacheService.set(token, iatDate);
        return token;
    }
    public static Map<String, Claim> verifyToken(String token) throws UnsupportedEncodingException, ValidTokenException {
//        Object obj = CaffCacheUtils.get(token);
//        IAuthCacheService authCacheService = SpringService.getApplicationContext().getBean(IAuthCacheService.class);
//        Object obj = authCacheService.get(token);
//        if(obj == null){
//            throw new ValidTokenException("登录凭证校验失败","token:" + token + "不存在或已经被注销");
//        }
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(SECRET)).build();
        DecodedJWT decodedJWT = null;
        try {
            decodedJWT = jwtVerifier.verify(token);
        }catch (Exception e){
            throw new ValidTokenException("登录凭证校验失败","token:" + token + "校验失败");
        }
        return decodedJWT.getClaims();
    }



}
package com.iscas.biz.security.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * <p>线程安全的时间转化工具类</p>
 * @author zhuquanwen
 * @version 1.0
 * @since jdk1.8
 * @date 2018/7/16
 **/
public class DateSafeUtils {
    private DateSafeUtils(){}
    public static final String PATTERN = "yyyy-MM-dd HH:mm:ss";
    /**
     * 锁对象
     */
    private static final Object LOCK_OBJ = new Object();
    /**
     * 存放不同的日期模板格式的sdf的Map
     */
    private static Map<String, ThreadLocal<SimpleDateFormat>> sdfMap = new HashMap<String, ThreadLocal<SimpleDateFormat>>();
    /**
     * 返回一个ThreadLocal的sdf,每个线程只会new一次sdf
     *
     * @param pattern
     * @return
     */
    private static SimpleDateFormat getSdf(final String pattern) {
        ThreadLocal<SimpleDateFormat> tl = sdfMap.get(pattern);
        // 此处的双重判断和同步是为了防止sdfMap这个单例被多次put重复的sdf
        if (tl == null) {
            synchronized (LOCK_OBJ) {
                tl = sdfMap.get(pattern);
                if (tl == null) {
                    // 只有Map中还没有这个pattern的sdf才会生成新的sdf并放入map
//                    System.out.println("put new sdf of pattern " + pattern + " to map");
                    // 这里是关键,使用ThreadLocal<SimpleDateFormat>替代原来直接new SimpleDateFormat
                    tl = new ThreadLocal<SimpleDateFormat>() {
                        @Override
                        protected SimpleDateFormat initialValue() {
                            return new SimpleDateFormat(pattern);
                        }
                    };
                    sdfMap.put(pattern, tl);
                }
            }
        }
        return tl.get();
    }
    /**
     * 使用ThreadLocal<SimpleDateFormat>来获取SimpleDateFormat,这样每个线程只会有一个SimpleDateFormat
     * 如果新的线程中没有SimpleDateFormat,才会new一个
     * @param date {@link Date} 时间
     * @param pattern 格式化串
     * @return String 时间字符串格式
     */
    public static String format(Date date, String pattern) {
        return getSdf(pattern).format(date);
    }
    /**
     * 使用ThreadLocal<SimpleDateFormat>来获取SimpleDateFormat,这样每个线程只会有一个SimpleDateFormat
     * 如果新的线程中没有SimpleDateFormat,才会new一个
     * @param dateStr 字符串
     * @param pattern 时间字符串格式
     * @throws ParseException 时间转换错误
     * @return Date {@link Date} 时间
     */
    public static Date parse(String dateStr, String pattern) throws ParseException {
        return getSdf(pattern).parse(dateStr);
    }
}
package com.iscas.biz.security.util;

import java.util.Date;

/**
 * <p>date操作增强类</p>
 * @author zhuquanwen
 * @version 1.0
 * @since jdk1.8
 */

public class DateRaiseUtils {

    private DateRaiseUtils(){}

    /**
     * 获取当前日期里的年份
     * @version 1.0
     * @since jdk1.8
     * @param date 日期
     * @return int 年份
     * @see DateSafeUtils
     */
    public static int getYear(Date date){
        assert date != null;
        String  x = DateSafeUtils.format(date, "yyyy");
        return Integer.parseInt(x);
    }

    /**
     * 获取当前日期里的月份
     * @version 1.0
     * @since jdk1.8
     * @param date 日期
     * @return int 月份
     * @see DateSafeUtils
     */
    public static int getMonth(Date date){
        assert date != null;
        String month = DateSafeUtils.format(date, "MM");
        return Integer.parseInt(month);
    }

    /**
     * 获取当前日期里的天
     * @version 1.0
     * @since jdk1.8
     * @param date 日期
     * @return int 天
     * @see DateSafeUtils
     */
    public static int getDay(Date date){
        assert date != null;
        String x = DateSafeUtils.format(date, "dd");
        return Integer.parseInt(x);
    }

    /**
     * 获取当前日期里的小时
     * @version 1.0
     * @since jdk1.8
     * @param date 日期
     * @return int 小时
     * @see DateSafeUtils
     */
    public static int getHour(Date date){
        assert date != null;
        String x = DateSafeUtils.format(date, "HH");
        return Integer.parseInt(x);
    }
    /**
     * 获取当前日期里的分钟
     * @version 1.0
     * @since jdk1.8
     * @param date 日期
     * @return int 分钟
     * @see DateSafeUtils
     */
    public static int getMinute(Date date){
        assert date != null;
        String x = DateSafeUtils.format(date, "HH");
        return Integer.parseInt(x);
    }

    /**
     * 获取当前日期里的秒数
     * @version 1.0
     * @since jdk1.8
     * @param date 日期
     * @return int 秒数
     * @see DateSafeUtils
     */
    public static int getSecond(Date date){
        assert date != null;
        String x = DateSafeUtils.format(date, "ss");
        return Integer.parseInt(x);
    }

//    /**
//     * 判断当前月份是否是季度末
//     * @version 1.0
//     * @since jdk1.8
//     * @param date 时间
//     * @return boolean
//     * @see #getMonth(Date)
//     */
    @SuppressWarnings("AlibabaUndefineMagicConstant")
//    public static boolean isSeason(Date date){
//        assert date != null;
//        boolean sign = false;
//        int month = getMonth(date);
//        if (month == MonthEnum.MAR.getValue()) {
//            sign = true;
//        }
//        if (month == MonthEnum.JULY.getValue()) {
//            sign = true;
//        }
//        if (month == MonthEnum.SEPT.getValue()) {
//            sign = true;
//        }
//        if (month == MonthEnum.DEC.getValue()) {
//            sign = true;
//        }
//        return sign;
//    }
    /**
     * 计算从现在开始偏移毫秒数后的时间,支持负数
     * @version 1.0
     * @since jdk1.8
     * @param offset 偏移的时候毫秒数
     * @return java.util.Date
     * @see #afterOffsetDate(Date, long)
     */
    public static Date afterOffsetDate(long offset){
        return afterOffsetDate(new Date(), offset);
    }

    /**
     * 计算从某个时间偏移毫秒数后的时间,支持负数
     * @version 1.0
     * @since jdk1.8
     * @param offset 偏移的时候毫秒数
     * @param date 日期时间
     * @return java.util.Date
     */
    public static Date afterOffsetDate(Date date, long offset){
        assert date != null;
        long time = date.getTime() * 1L;
        time = time + offset;
        return new Date(time);
    }

    /**
     * 时间偏移一定毫秒数
     * @see {@link #afterOffsetDate(Date, long)}
     * */
    @Deprecated
    public static Date timeOffset(Date time, long offset) {
        time = new Date(time.getTime() + offset);
        return time;
    }
}
package com.iscas.common.web.tools.json;

import cn.miludeer.jsoncode.JsonCode;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.collections.CollectionUtils;


import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * @program: stc-pub
 * @description: JSON工具类
 * @author: LiangJian
 * @create: 2018-08-29 09:56
 **/
public class JsonUtils {
    private static ObjectMapper mapper;


    /**
     * 对象转json
     *
     * @param object
     * @return
     */
    public static String toJson(Object object){
        try {
            return getMapper().writeValueAsString(object);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
//			throw new DataSongException(Status.PARAM_ERROR, String.format("object to json error: [%s]",DataSongExceptionUtils.getExceptionInfo(e)));
        }
//        return null;
    }

    public static <T> T fromJson(String json, Class<T > classOfT) {
        //				return gson.fromJson(json, classOfT);
        try {
            return getMapper().readValue(json, classOfT);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
//        return null;
    }

    /**
     * @param json
     * @param typeReference
     * @param <T>           new TypeReference<HashMap<String,Field>>() {}
     * @return
     */
    public static <T> T fromJson(String json, TypeReference typeReference) {
        try {
            return getMapper().readValue(json, typeReference);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
//        return null;
    }

    /**
     * 定义一个嵌套的泛型、子泛型
     * */
    static class ParametricTypes {
        /**
         * 泛型1
         * */
        private Class clazz;

        /**
         * 子泛型
         * */
        private List<ParametricTypes> subClazz;

        public Class getClazz() {
            return clazz;
        }

        public void setClazz(Class clazz) {
            this.clazz = clazz;
        }

        public List<ParametricTypes> getSubClazz() {
            return subClazz;
        }

        public void setSubClazz(List<ParametricTypes> subClazz) {
            this.subClazz = subClazz;
        }
    }

    /**
     * 嵌套一层泛型序列化
     */
    public static <T> T fromJson(String json, Class mainClass, Class subClass) {
        try {
            JavaType javaType = getMapper().getTypeFactory().constructParametricType(mainClass, subClass);
            return getMapper().readValue(json, javaType);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }

    /**
     * 嵌套泛型序列化
     */
    public static <T> T fromJson(String json, ParametricTypes parametricTypes) {
        try {
//            getMapper().getTypeFactory().constructParametricType()
            JavaType javaType = getJavaType(parametricTypes);
            return getMapper().readValue(json, javaType);
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }
//        return null;
    }

    private static JavaType getJavaType(ParametricTypes parametricTypes) {
        JavaType javaType = null;
        Class clazz = parametricTypes.getClazz();
        List<ParametricTypes> subClazz = parametricTypes.getSubClazz();
        if (CollectionUtils.isEmpty(subClazz)) {
            Class[] classes = new Class[0];
            javaType = getMapper().getTypeFactory().constructParametricType(clazz, classes);
        } else {
            JavaType[] javaTypes = new JavaType[subClazz.size()];
            for (int i = 0; i < subClazz.size(); i++) {
                JavaType jt = getJavaType(subClazz.get(i));
                javaTypes[i] = jt;
            }
            javaType = getMapper().getTypeFactory().constructParametricType(clazz, javaTypes);
        }

        return javaType;
    }


    private static ObjectMapper getMapper() {
        if (mapper == null) {
            mapper = new ObjectMapper();
			/*ObjectMapper configure = mapper
				.configure(org.codehaus.jackson.map.DeserializationConfig.Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT,
					true);*/
            //为null的不输出
            mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
            //大小写问题
            mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true);

            //设置等同于@JsonIgnoreProperties(ignoreUnknown = true)
            mapper.setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY);
            mapper.setVisibility(PropertyAccessor.GETTER, JsonAutoDetect.Visibility.NONE);//防止转为json是首字母大写的属性会出现两次
            mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);

            //设置JSON时间格式
            SimpleDateFormat myDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            mapper.setDateFormat(myDateFormat);

//			mapper.configure(SerializationFeature.WRAP_ROOT_VALUE CLOSE_CLOSEABLE)
        }

        return mapper;

    }

    /**
     * 单位缩进字符串。
     */
    private static String SPACE = "\t";

    /**
     * 返回格式化JSON字符串。
     *
     * @param json 未格式化的JSON字符串。
     * @return 格式化的JSON字符串。
     */
    public static String formatJson(String json)
    {
        StringBuffer result = new StringBuffer();

        int length = json.length();
        int number = 0;
        char key = 0;

        //遍历输入字符串。
        for (int i = 0; i < length; i++)
        {
            //1、获取当前字符。
            key = json.charAt(i);

            //2、如果当前字符是前方括号、前花括号做如下处理:
            if((key == '[') || (key == '{') )
            {
                //(1)如果前面还有字符,并且字符为“:”,打印:换行和缩进字符字符串。
                if((i - 1 > 0) && (json.charAt(i - 1) == ':'))
                {
                    result.append('\n');
                    result.append(indent(number));
                }

                //(2)打印:当前字符。
                result.append(key);

                //(3)前方括号、前花括号,的后面必须换行。打印:换行。
                result.append('\n');

                //(4)每出现一次前方括号、前花括号;缩进次数增加一次。打印:新行缩进。
                number++;
                result.append(indent(number));

                //(5)进行下一次循环。
                continue;
            }

            //3、如果当前字符是后方括号、后花括号做如下处理:
            if((key == ']') || (key == '}') )
            {
                //(1)后方括号、后花括号,的前面必须换行。打印:换行。
                result.append('\n');

                //(2)每出现一次后方括号、后花括号;缩进次数减少一次。打印:缩进。
                number--;
                result.append(indent(number));

                //(3)打印:当前字符。
                result.append(key);

                //(4)如果当前字符后面还有字符,并且字符不为“,”,打印:换行。
                if(((i + 1) < length) && (json.charAt(i + 1) != ','))
                {
                    result.append('\n');
                }

                //(5)继续下一次循环。
                continue;
            }

            //4、如果当前字符是逗号。逗号后面换行,并缩进,不改变缩进次数。
            if((key == ','))
            {
                result.append(key);
                result.append('\n');
                result.append(indent(number));
                continue;
            }

            //5、打印:当前字符。
            result.append(key);
        }

        return result.toString();
    }

    /**
     * 返回指定次数的缩进字符串。每一次缩进三个空格,即SPACE。
     *
     * @param number 缩进次数。
     * @return 指定缩进次数的字符串。
     */
    private static String indent(int number)
    {
        StringBuffer result = new StringBuffer();
        for(int i = 0; i < number; i++)
        {
            result.append(SPACE);
        }
        return result.toString();
    }

    /**
     *
     * 校验一个JSON串是否为JSON结构,必须满足Map或集合结构
     * */
    public static boolean validateJson(String json) {
        boolean flag = false;
        try {
            JsonUtils.fromJson(json, Map.class);
            return true;
        } catch (Exception e) {
        }
        try {
            JsonUtils.fromJson(json, List.class);
            return true;
        } catch (Exception e) {
        }
        return flag;

    }

    /**
     *  向JSON中追加参数
     *  注意:只支持Map类型的JSON
     *
     * @param json 原始JSON字符串。
     * @param data 要添加的数据,数组类型,数组里两个值,第一个值为key,第二个值为value
     * @return 追加后的JSON字符串。
     */
    public static String appendJson(String json, Object[] ... data) throws RuntimeException {
        Map map = null;
        try {
            map = JsonUtils.fromJson(json, Map.class);
        } catch (Exception e) {
            throw new RuntimeException("JSON格式错误,只支持Map格式的JSON", e);
        }
        if (data != null) {
            for (Object[] datum : data) {
                if (datum == null || datum.length != 2) {
                    throw new RuntimeException("传入的追加格式错误");
                }
                map.put(datum[0], datum[1]);
            }
        }
        return toJson(map);

    }

    public static JsonArray createJsonArray() {
        return new JsonArray();
    }

    public static JsonObject createJsonObject() {
        return new JsonObject();
    }

    public static Object convertValue(Object value) {
        Object convertData = null;
        if (value instanceof JsonObject) {
            JsonObject jo = (JsonObject) value;
            convertData = jo.toMap();
        } else if (value instanceof JsonArray) {
            JsonArray jsonArray = (JsonArray) value;
            convertData = jsonArray.toList();
        } else if (value instanceof List) {
            String s = JsonUtils.toJson(value);
            convertData = JsonUtils.fromJson(s, List.class);
        } else if (value instanceof Integer ||
                value instanceof Double ||
                value instanceof Character ||
                value instanceof Float ||
                value instanceof Short ||
                value instanceof Byte ||
                value instanceof Boolean ||
                value instanceof String ||
                value instanceof Long) {
            //基本数据类型不做处理
            convertData = value;
        } else {
            //Map 和JavaBean都转为Map
            //注意这样不能保证Map的顺序
            try {
                String s = JsonUtils.toJson(value);
                convertData = JsonUtils.fromJson(s, Map.class);
            } catch (Exception e) {
                throw new RuntimeException("转化出错,不能转为Map结构", e);
            }
        }
        return convertData;
    }

    /**
     *
     * 获取JSON中的一个数据,字符串形式
     * */
    public static String getValueByKey(String json, String key) {
        return JsonCode.getValue(json, String.format("$.%s", key));
    }

}
package com.iscas.templet.common;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import lombok.experimental.Accessors;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * @Author: zhuquanwen
 * @Description:
 * @Date: 2017/12/25 16:41
 * @Modified:
 **/
@Data
@ToString(callSuper = true)
@Accessors(chain = true)
public class ResponseEntity<T> implements Serializable {

    /**
     * http状态码
     */
//    protected Integer status;
    /**
     * 状态信息
     */
    protected String message;

    /**
     * 服务器内部错误描述
     */
    protected String desc;

    /**
     * 返回值
     */
    protected T value;

    /**
     * 访问URL
     */
    protected String requestURL;

    protected long tookInMillis;

    protected int total;

    public ResponseEntity(Integer status, String message) {
        super();
//        this.status = status;
        this.message = message;
    }

    public ResponseEntity() {
        super();
//        this.status = 200;
        this.message = "操作成功";
    }
    public ResponseEntity(String message){
        super();
        this.message = message;
    }


}

15、启动类

package com.iscas.biz.security;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

/**
 *
 * @author zhuquanwen
 * @vesion 1.0
 * @date 2021/2/27 15:09
 * @since jdk1.8
 */
@SpringBootApplication
public class BizSecurity extends SpringBootServletInitializer {
    public static void main(String[] args) {
        SpringApplication springApplication = new SpringApplication(BizSecurity.class);
        springApplication.run(args);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        return builder.sources(BizSecurity.class);
    }
}

16、配置文件

spring.datasource.url=jdbc:mysql://localhost:3306/springboot?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&useSSL=false&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

spring.jpa.database=mysql
spring.jpa.show-sql=true

17、测试

(1)未登录的情况访问/admin/details和/admin2/details

提示未登录

前后端分离 springboot 登录验证 springboot springsecurity前后端分离_java


前后端分离 springboot 登录验证 springboot springsecurity前后端分离_spring_02

(2)未登录的情况访问/admin3/details

因为admin3不做权限控制,可以访问

前后端分离 springboot 登录验证 springboot springsecurity前后端分离_security_03

(3)登录

使用spring security默认的登录接口,表单传入username和password,如我预期,生成了token,并返回,前端可以将此token存入localstorage

前后端分离 springboot 登录验证 springboot springsecurity前后端分离_spring_04


(4)登录后访问/admin/detail

在header中携带登录返回的token

前后端分离 springboot 登录验证 springboot springsecurity前后端分离_spring_05


(5)登录后访问/admin2/detail

因为在WebSecurityConfig的配置中,/admin2/**需要AAA的角色,而admin没有此角色,固鉴权失败,并返回了提示的JSON,而不是页面

前后端分离 springboot 登录验证 springboot springsecurity前后端分离_springsecurity_06

另外,资源权限的配置提供一种方式:

在WebSecurityConfig中将硬代码的配置去掉,打开access,如下图

前后端分离 springboot 登录验证 springboot springsecurity前后端分离_java_07


创建access中对应的类和函数

package com.iscas.biz.security.service;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import javax.servlet.http.HttpServletRequest;
import java.util.HashSet;
import java.util.Set;

@Component("rbacauthorityservice")
public class RbacAuthorityService {
    public boolean hasPermission(HttpServletRequest request, Authentication authentication) {

        Object userInfo = authentication.getPrincipal();

        boolean hasPermission  = false;

        if (userInfo instanceof UserDetails) {

            String username = ((UserDetails) userInfo).getUsername();

            //获取资源
            Set<String> urls = new HashSet();
            urls.add("/common/**"); // 这些 url 都是要登录后才能访问,且其他的 url 都不能访问!

            AntPathMatcher antPathMatcher = new AntPathMatcher();

            for (String url : urls) {
                if (antPathMatcher.match(url, request.getRequestURI())) {
                    hasPermission = true;
                    break;
                }
            }

            return hasPermission;
        } else {
            return false;
        }
    }
}