0
点赞
收藏
分享

微信扫一扫

SpringSecurity动态授权(前后分离)

guanguans 2022-04-24 阅读 38
springjava

基于SpringSecurity的动态授权

第一步:创建数据库

在这里插入图片描述

第二步:运行SQL文件

/*
 Navicat Premium Data Transfer

 Source Server         : MySQL
 Source Server Type    : MySQL
 Source Server Version : 50637
 Source Host           : localhost:3306
 Source Schema         : spring-security

 Target Server Type    : MySQL
 Target Server Version : 50637
 File Encoding         : 65001

 Date: 08/07/2020 20:55:02
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for menu
-- ----------------------------
DROP TABLE IF EXISTS `menu`;
CREATE TABLE `menu`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `pattern` varchar(50) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of menu
-- ----------------------------
INSERT INTO `menu` VALUES (1, '/db/**');
INSERT INTO `menu` VALUES (2, '/admin/**');
INSERT INTO `menu` VALUES (3, '/user/**');

-- ----------------------------
-- Table structure for menu_role
-- ----------------------------
DROP TABLE IF EXISTS `menu_role`;
CREATE TABLE `menu_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `mid` int(11) NOT NULL,
  `rid` int(11) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of menu_role
-- ----------------------------
INSERT INTO `menu_role` VALUES (1, 1, 1);
INSERT INTO `menu_role` VALUES (2, 2, 2);
INSERT INTO `menu_role` VALUES (3, 3, 3);

-- ----------------------------
-- Table structure for role
-- ----------------------------
DROP TABLE IF EXISTS `role`;
CREATE TABLE `role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `nameZh` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of role
-- ----------------------------
INSERT INTO `role` VALUES (1, 'ROLE_dba', '数据库管理员');
INSERT INTO `role` VALUES (2, 'ROLE_admin', '系统管理员');
INSERT INTO `role` VALUES (3, 'ROLE_user', '用户');

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
  `enabled` tinyint(1) NOT NULL DEFAULT 1,
  `locked` tinyint(1) NOT NULL DEFAULT 0,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'root', '123', 0, 0);
INSERT INTO `user` VALUES (2, 'admin', '123', 0, 0);
INSERT INTO `user` VALUES (3, 'user', '123', 0, 0);

-- ----------------------------
-- Table structure for user_role
-- ----------------------------
DROP TABLE IF EXISTS `user_role`;
CREATE TABLE `user_role`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `uid` int(11) NOT NULL,
  `rid` int(11) NOT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact;

-- ----------------------------
-- Records of user_role
-- ----------------------------
INSERT INTO `user_role` VALUES (1, 1, 1);
INSERT INTO `user_role` VALUES (2, 1, 2);
INSERT INTO `user_role` VALUES (3, 1, 3);
INSERT INTO `user_role` VALUES (4, 2, 2);
INSERT INTO `user_role` VALUES (5, 2, 3);
INSERT INTO `user_role` VALUES (6, 3, 3);

SET FOREIGN_KEY_CHECKS = 1;

第三步: 创建项目导入依赖

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.70</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.0</version>
        </dependency>
    </dependencies>

第四步: 配置文件

server.port=9090
server.tomcat.uri-encoding=UTF-8
spring.application.name=security_demo
#连接MySQL数据库路径(设置utf-8字符集,解决中文乱码问题;设置中国时区,解决前后台时间不一致问题)
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/spring-security?useUnicode=true&characterEncoding=utf8&serverTimezone=CTT
#连接MySQL用户名
spring.datasource.username=root
#连接MySQL密码
spring.datasource.password=123456
#mysql驱动
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#mybatis映射的sql文件
mybatis.mapper-locations=classpath:mapper/*.xml
#开启sql日志显示
logging.level.com.xiaohei=debug

第五步: 创建实体类

package com.xiaohei.security_demo.entity;

/**
 * @ClassName: LoginUser
 * @Description:
 * @Author: xiaohei on Date:2022/4/23 23:54
 * @Version: 1.0
 */
public class LoginUser {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

package com.xiaohei.security_demo.entity;

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

/**
 * @ClassName: Menu
 * @Description:
 * @Author: xiaohei on Date:2022/4/23 14:41
 * @Version: 1.0
 */
public class Menu implements Serializable {
    private Integer id;
    private String pattern;
    private List<Role> roles;  //访问这个路径需要的角色

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getPattern() {
        return pattern;
    }

    public void setPattern(String pattern) {
        this.pattern = pattern;
    }

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }
}

package com.xiaohei.security_demo.entity;

import java.io.Serializable;

/**
 * @ClassName: Role
 * @Description:
 * @Author: xiaohei on Date:2022/4/23 14:39
 * @Version: 1.0
 */
public class Role implements Serializable {
    private Integer id;
    private String name;
    private String nameZh;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNameZh() {
        return nameZh;
    }

    public void setNameZh(String nameZh) {
        this.nameZh = nameZh;
    }

}

package com.xiaohei.security_demo.entity;


import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

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

/**
 * @ClassName: User
 * @Description:
 * @Author: xiaohei on Date:2022/4/23 14:43
 * @Version: 1.0
 */
public class User implements Serializable, UserDetails {

    private static final Logger log = LoggerFactory.getLogger(User.class);
    private Integer id;
    private String username;
    private String password;
    private Boolean enabled; //是否启用
    private Boolean locked; //这个用户是否被锁
    private List<Role> roles;

    public Integer getId() {
        return id;
    }

    //获取当前用户对象所具有的角色信息
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        System.out.println("-------> User.getAuthorities()");
        List<SimpleGrantedAuthority> authority = new ArrayList<>();
        for (Role role : this.roles) {
            authority.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authority;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    public Boolean getEnabled() {
        return enabled;
    }

    public void setEnabled(Boolean enabled) {
        this.enabled = enabled;
    }

    public Boolean getLocked() {
        return locked;
    }

    public void setLocked(Boolean locked) {
        this.locked = locked;
    }

    //当前账号是否过期
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public List<Role> getRoles() {
        return roles;
    }

    public void setRoles(List<Role> roles) {
        this.roles = roles;
    }

    //当前账号密码是否过期
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    //当前账号密码是否未锁定
    @Override
    public boolean isCredentialsNonExpired() {
        return !this.getLocked();
    }

    //当前用户是否未可用
    @Override
    public boolean isEnabled() {
        return !this.getEnabled();
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", enabled=" + enabled +
                ", locked=" + locked +
                ", roles=" + roles +
                '}';
    }
}

package com.xiaohei.security_demo.util;


import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;

public class TokenUtils {
    //定义token的header的名称
    public static final String TOKEN_HEADER = "Authorization";
    //按照jwt的规定使用Bearer token 来发送
    public static final String TOKEN_PREFIX = "Bearer ";

    //加密所需的加密密钥
    private static final String SECRET = "xiaohei";
    private static final String ISS = "echisan";
    // 角色的key
    private static final String ROLE_CLAIMS = "rol";

    // 过期时间是3600秒,既是1个小时
    private static final long EXPIRATION = 3600L;

    // 选择了记住我之后的过期时间为7天
    private static final long EXPIRATION_REMEMBER = 604800L;
    // 创建token
    public static String createToken(String username) {
        //long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
        return Jwts.builder()
                .signWith(SignatureAlgorithm.HS512, SECRET)
                .setIssuer(ISS)
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date(System.currentTimeMillis()  * 1000))
                .compact();
    }

    //从token中获取用户名
    public static String getUsername(String token){
        return getTokenBody(token).getSubject();
    }

//    //获取用户角色
//    public static String getUserRole(String token){
//        return (String)getTokenBody(token).get(ROLE_CLAIMS);
//    }

    //是否过期
    public static boolean isExpiration(String token){
        try{
            //获取过期时间在对比现在的时间
            return getTokenBody(token).getExpiration().before(new Date());
        }catch (ExpiredJwtException e){
            return true;  //返回true为过期了
        }
    }


    //解析token,并拿到其中带的信息
    public static Claims getTokenBody(String token){
        return Jwts.parser()
                .setSigningKey(SECRET)  //加密于解密的对应字符串要一致
                .parseClaimsJws(token)  //加密之后的token值
                .getBody();
    }
}

第六步: 创建mapper与dao

package com.xiaohei.security_demo.dao;

import com.xiaohei.security_demo.entity.Menu;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface MenuMapper {
    List<Menu> getAllMenus();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xiaohei.security_demo.dao.MenuMapper">
    <resultMap id="baseResultMap" type="com.xiaohei.security_demo.entity.Menu">
        <id property="id" column="id"/>
        <result property="pattern" column="pattern"/>
        <collection property="roles" ofType="com.xiaohei.security_demo.entity.Role">
            <id property="id" column="rid"/>
            <result property="name" column="rname"/>
            <result property="nameZh" column="rnameZh"/>
        </collection>
    </resultMap>

    <select id="getAllMenus" resultMap="baseResultMap">
        select m.id, m.pattern, r.id as rid, r.name as rname, r.nameZh as rnameZh
        from menu m
                 left join menu_role mr on m.id = mr.mid
                 left join role r on mr.rid = r.id;
    </select>
</mapper>
package com.xiaohei.security_demo.dao;

import com.xiaohei.security_demo.entity.Role;
import com.xiaohei.security_demo.entity.User;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface UserMapper {
    User loadUserByUsername(String username);

    List<Role> getUserRolesByUid(Integer id);

    User login(String username, String password);
}

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xiaohei.security_demo.dao.UserMapper">
    <select id="loadUserByUsername" resultType="com.xiaohei.security_demo.entity.User"
            parameterType="java.lang.String">
        select * from user where username=#{username}
    </select>
    <select id="getUserRolesByUid" parameterType="java.lang.Integer"
            resultType="com.xiaohei.security_demo.entity.Role">
        select r.id,r.name,r.nameZh from role r,user_role ur where r.id=ur.rid and ur.uid=#{id}
    </select>

    <select id="login"  parameterType="java.lang.String" resultType="com.xiaohei.security_demo.entity.User">
        select * from user where username=#{username} and password=#{password}
    </select>
</mapper>
package com.xiaohei.security_demo.service.impl;

import com.alibaba.fastjson.JSON;
import com.xiaohei.security_demo.dao.UserMapper;
import com.xiaohei.security_demo.entity.User;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;

/**
 * @ClassName: UserServiceImpl
 * @Description:
 * @Author: xiaohei on Date:2022/4/23 15:05
 * @Version: 1.0
 */
@Component
public class UserServiceImpl implements UserDetailsService {
    private static final Logger LOG = LoggerFactory.getLogger(UserServiceImpl.class);

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private PasswordEncoder passwordEncoder;

    //在用户登录时会自动来调用这个方法
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        System.out.println("------------------------> UserService.loadUserByUsername()");
        User user = userMapper.loadUserByUsername(username);
        if (user == null){
            throw new UsernameNotFoundException("用户不存在");
        }
        //拿到用户的角色
        user.setRoles(userMapper.getUserRolesByUid(user.getId()));
        System.out.println("----- user ------>"+ JSON.toJSONString(user));
        user.setPassword(passwordEncoder.encode(user.getPassword()));
//        LOG.info("------ user ----->{}",JSON.toJSONString(user));
        return user;
    }
}

第七步:实现FilterInvocationSecurityMetadataSource

package com.xiaohei.security_demo.filter;

import com.alibaba.fastjson.JSON;
import com.xiaohei.security_demo.dao.MenuMapper;
import com.xiaohei.security_demo.entity.Menu;
import com.xiaohei.security_demo.entity.Role;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @ClassName: CustomerFilterInvocationSecurityMetadataSource
 * @Description:
 * @Author: xiaohei on Date:2022/4/23 15:16
 * @Version: 1.0
 */
@Component
public class CustomerFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {

    @Autowired
    private MenuMapper menuMapper;
    private static final Logger log = LoggerFactory.getLogger(CustomerFilterInvocationSecurityMetadataSource.class);
    private static List<Menu> allMenus = null;
    /**
     * 获取所有的资源信息
     * <p>
     * 在getAttributes()方法内不可直接使用menuMapper,因为Security先于Spring加载,此时还没有注入,会报空指针异常
     * 被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次,依赖注入初始化后会自动执行该方法
     */

    @PostConstruct
    private void getMenus(){

//            log.info(" ------------ menuMapper ----------> \n {}",menuMapper);
            System.out.println(" ------------ menuMapper -------->"+menuMapper);
            allMenus = menuMapper.getAllMenus();
//            log.info(" ------------ allMenus ----------> \n {}",JSON.toJSONString(allMenus));
            System.out.println(" ------------ allMenus ----------> \n"+ JSON.toJSONString(allMenus));
    }

    //getAttributes这个在被SpringSecurity管理
    //每次请求有权限认证的接口时,就会调用这个方法

    /**
     *这个方法具体做了什么捏?
     *      当前端请求一个url这个方法就会启动,通过FilterInvocation.getRequestUrl()获取到请求的URL地址
     *      在通过这个URL和数据库里面的路径进行匹配
     *      如果匹配成功,就给这个URL进行授权
     *      也就是这个URL要什么角色或者什么权限才能进行访问
     */

    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
        AntPathMatcher antPathMatcher = new AntPathMatcher();
        System.out.println("*********************************************************");
        System.out.println("---------------------> getAttributes  ------------------->");
        String [] roleAll = new String[]{};
        String url = ((FilterInvocation)object).getRequestUrl();
        System.out.println("----------> url --------->"+ url);
//        log.info("----------> url --------->{}",url);
        for (Menu menu : allMenus) {
            if (antPathMatcher.match(menu.getPattern(),url)){
                //拿到所有路径需要的角色
                List<Role> roles = menu.getRoles();
                roleAll = new String[roles.size()];
                for (int i = 0; i < roleAll.length; i++) {
                    roleAll[i] = roles.get(i).getName();
                }
            }
        }
        return SecurityConfig.createList(roleAll);
    }

    //返回所有定义好的权限资源
    //Spring Security 在启动时会校验相关配置是否正确,如果不需要校验,直接返回null
    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        System.out.println("-----------------getAllConfigAttributes-----------");
        return null;
    }

    @Override
    public boolean supports(Class<?> clazz) {
        System.out.println("-----------------supports-----------");

        return true;
    }
}

第八步: 实现AccessDecisionManager

package com.xiaohei.security_demo.filter;

import com.alibaba.fastjson.JSON;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.Collection;

/**
 * @ClassName: CustomerAccessDeciisionManger
 * @Description:
 * @Author: xiaohei on Date:2022/4/23 15:32
 * @Version: 1.0
 */
public class CustomerAccessDecisionManger implements AccessDecisionManager {

    /**
     * 判断当前登录的用户是否具备当前请求URL所需要的角色信息
     * 如果不具备,就抛出 AccessDeniedException 异常,否则不做任何事即可
     *
     * @param authentication 当前登录用户的信息
     * @param object         FilterInvocation对象,可以获取当前请求对象
     * @param configAttributes  FilterInvocationSecurityMetadataSource 中的 getAttributes() 方法的返回值,++-
     *                       即当前请求URL所需的角色
     */
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        // 当接口未被配置资源时直接放行
        if (configAttributes.size() == 0) {
            return;
        }
        System.out.println(" -------------> AccessDecisionManager.decide------>");

        //这个是当前用户的角色
        Collection<? extends GrantedAuthority> auths = authentication.getAuthorities();

        //这个是接口的权限
        for (ConfigAttribute configAttribute : configAttributes) {
            System.out.println("-----url_role_required-----> " + configAttribute.getAttribute());
            System.out.println("-----auths-----> " + JSON.toJSONString(auths));
            String attribute = configAttribute.getAttribute();
            //
            for (GrantedAuthority auth : auths) {
                //这里判断只要这个用户有其中一个权限就代表他是可以访问这个接口的
                if (attribute.trim().equals(auth.getAuthority())){
                    return;
                }
            }
        }
        throw new AccessDeniedException("抱歉,您没有访问权限");
    }

    //是否支持校验
    @Override
    public boolean supports(ConfigAttribute attribute) {
        return true;
    }

    //是否支持校验
    @Override
    public boolean supports(Class<?> clazz) {
        return true;
    }
}

第八步: OncePerRequestFilter

package com.xiaohei.security_demo.filter;

import com.xiaohei.security_demo.entity.User;
import com.xiaohei.security_demo.util.TokenUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
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 org.springframework.web.filter.OncePerRequestFilter;

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

/**
 * @ClassName: JWTAuthorizationTokenFilter
 * @Description:
 * @Author: xiaohei on Date:2022/4/23 17:03
 * @Version: 1.0
 */
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {

    @Autowired(required = false)
    private UserDetailsService userDetailsService;

//    public JwtAuthenticationTokenFilter(AuthenticationManager authenticationManager) {
//        super(authenticationManager);
        this.userDetailsService = userDetailsService;
//    }


    /**
     * 这个方法在用户访问带权限的方法是会来调用
     *  这个方法就是识别Token 并吧Token解析获取到用户名
     *  通过用户名来获取用户的权限
     *  并调用AccessDecisionManager中的decide方法来进行
     *  判断这个用户是否有执行权限
     */
    
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        //从headr获取token
        String token = request.getHeader(TokenUtils.TOKEN_HEADER);
        if (token!=null && !token.equals("")){
            token = token.replace(TokenUtils.TOKEN_PREFIX, "");
            System.out.println(userDetailsService);
            String username = TokenUtils.getUsername(token);
            System.out.println(username);
            UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        filterChain.doFilter(request,response);
    }
}

第九步: 编写自定义的Exception

package com.xiaohei.security_demo.exception;

import com.alibaba.fastjson.JSON;
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;

@Component
public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException, IOException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().println(JSON.toJSONString(authException.getMessage()));
        response.getWriter().flush();
    }
}
package com.xiaohei.security_demo.exception;

import com.alibaba.fastjson.JSON;
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;
/**
 * 当访问接口没有权限时,自定义的返回结果
 * Created by macro on 2018/4/26.
 */
@Component
public class RestfulAccessDeniedHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest request,
                       HttpServletResponse response,
                       AccessDeniedException e) throws IOException, ServletException {
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json");
        response.getWriter().println(JSON.toJSONString(e.getMessage()));
        response.getWriter().flush();
    }
}

第十步: 编写SpringSecurity的Config文件

package com.xiaohei.security_demo.config;

import com.xiaohei.security_demo.exception.RestAuthenticationEntryPoint;
import com.xiaohei.security_demo.exception.RestfulAccessDeniedHandler;
import com.xiaohei.security_demo.filter.CustomerAccessDecisionManger;
import com.xiaohei.security_demo.filter.CustomerFilterInvocationSecurityMetadataSource;
import com.xiaohei.security_demo.filter.JWTAuthorizationTokenFilter;
import com.xiaohei.security_demo.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.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;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * @ClassName: SecurityConfig
 * @Description:
 * @Author: xiaohei on Date:2022/4/23 17:22
 * @Version: 1.0
 */

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Qualifier("userServiceImpl")
    @Autowired(required = false)
    private UserDetailsService userDetailsService;

    @Autowired
    private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
    @Autowired
    private RestAuthenticationEntryPoint restAuthenticationEntryPoint;

    @Autowired
    private CustomerFilterInvocationSecurityMetadataSource customerFilterInvocationSecurityMetadataSource;

    @Bean
    public PasswordEncoder passwordEncoder() {
        System.out.println("----------------> MyWebSecurityConfig.passwordEncoder()");
        return new BCryptPasswordEncoder(15);
    }

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

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.cors().and().csrf().disable()
                .authorizeRequests()
//                .antMatchers(HttpMethod.DELETE, "/tasks/**").hasRole("ADMIN")
                // 测试用资源,需要验证了的用户才能访问
                // HttpSecurity配置,可以通过URL获取对应的角色信息
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setSecurityMetadataSource(customerFilterInvocationSecurityMetadataSource);
                        object.setAccessDecisionManager(new CustomerAccessDecisionManger());
                        return object;
                    }
                })
                //动态授权,就是让这个配置向上抽取
//                .antMatchers("/admin/**").hasRole("Role_admin")
//                .antMatchers("/user/**").hasRole("Role_user")
//                .antMatchers("/user/**").hasRole("Role_admin")
                // 除了上面外的所有请求全部需要认证
                .and()
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, // 允许对于网站静态资源的无授权访问
                        "/",
                        "/*.html",
                        "/favicon.ico",
                        "/**/*.html",
                        "/**/*.css",
                        "/**/*.js",
                        "/swagger-resources/**",
                        "/v2/api-docs/**"
                )
                .permitAll()
                .antMatchers("/auth/login", "/admin/register")// 对登录注册要允许匿名访问
                .permitAll()
                .antMatchers(HttpMethod.OPTIONS)//跨域请求会先进行一次options请求
                .permitAll()
                .anyRequest()
                .authenticated()
                .and()
//                .addFilter(new JWTAuthorizationTokenFilter(authenticationManager()))
                .httpBasic().and()
                // 不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .exceptionHandling();

        http.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
        //添加自定义未授权和未登录结果返回
        http.exceptionHandling()
                .accessDeniedHandler(restfulAccessDeniedHandler)
                .authenticationEntryPoint(restAuthenticationEntryPoint);
    }

    //不进行认证的路径,可以直接访问
    @Override
    public void configure(WebSecurity web) throws Exception {
        web.ignoring().antMatchers("/api/**");
    }

    //不在这里注入容器的话 Filter不能自动装配@Autowired
    @Bean
    public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){
        return new JwtAuthenticationTokenFilter();
    }
}

package com.xiaohei.security_demo.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    /**
     * 开启跨域
     */
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 设置允许跨域的路由
        registry.addMapping("/**")
                // 设置允许跨域请求的域名
                .allowedOrigins("*")
                // 是否允许证书(cookies)
                .allowCredentials(true)
                // 设置允许的方法
                .allowedMethods("*")
                // 跨域允许时间
                .maxAge(3600);
    }

}

第十一步: 最激动人心的Controller文件

package com.xiaohei.security_demo.controller;

import com.xiaohei.security_demo.dao.UserMapper;
import com.xiaohei.security_demo.entity.LoginUser;
import com.xiaohei.security_demo.entity.User;
import com.xiaohei.security_demo.util.TokenUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestController
@CrossOrigin
public class TestSecurityController {

    @Autowired
    UserMapper userMapper;


    @GetMapping("/admin/xiaohei")
    public String getAll(){
        return "我是大帝";
    }

    @GetMapping("/api/xiaohie")
    public String getNoAll(){
        return "我是小弟";
    }

    @PostMapping("/user/user")
    public String getXiaohei(){
        return "我是xiaohei";
    }

	//这个是你们自己定义的登录方法,大家可以自己修改
	
    @PostMapping("/auth/login")
    public Map<String,String> login(@RequestBody LoginUser login){
        System.out.println(this.passwordEncoder);
        System.out.println(login.getUsername());
        //$2a$15$1PBhWFNdBnEz5VvlNW47V.fB1/JFsw8hgvGXqrBWBDJv9gTZGgH.C
        System.out.println(passwordEncoder.encode(login.getPassword()));
        User user = userMapper.login(login.getUsername(),login.getPassword());
        Map<String,String> map = new HashMap<String,String>();
        if (user == null){
            map.put("201","用户名或者密码错误");
            return map;
        }
        String token = TokenUtils.createToken(user.getUsername());
        map.put("token",token);
        map.put("tokenHead",TokenUtils.TOKEN_PREFIX);
        return map;
    }
}

最后: 你们可能最想要的前端代码,哈哈哈

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>图片上传</title>
    <!-- 引入样式文件 -->
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vant@2.12/lib/index.css" />
    <!-- 引入 Vue 和 Vant 的 JS 文件 -->
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6/dist/vue.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/vant@2.12/lib/vant.min.js"></script>
    <script src="https://unpkg.com/axios/dist/axios.min.js"></script>
    <style>

    </style>
</head>

<body>
    <div id="app">
        <van-form @submit="onSubmit">
            <div>

                <van-field v-model="username" name="username" label="用户名" placeholder="用户名"
                    :rules="[{ required: true, message: '请填写用户名' }]" />
            </div>
            <div>

                <van-field v-model="password" type="password" name="password" label="密码" placeholder="密码"
                    :rules="[{ required: true, message: '请填写密码' }]" />
            </div>
            <div style="margin: 16px;">
                <van-button round block type="info" native-type="submit">提交</van-button>
            </div>
        </van-form>
        <button @click="api">admin权限请求</button>
        <button @click="xiaohei">xiaohei权限请求</button>
    </div>
    <script>
        // 在 #app 标签下渲染一个按钮组件
        new Vue({
            el: '#app',
            data() {
                return {
                    username: '',
                    password: '',
                    token: ''
                }

            },
            mounted() {
                // http request拦截器 添加一个请求拦截器
                // axios.interceptors.request.use(function (config) {
                //     // Do something before request is sent
                //     //window.localStorage.getItem("accessToken") 获取token的value
                //     // let token = window.localStorage.getItem("accessToken")
                //     if (this.token) {
                //         //将token放到请求头发送给服务器,将tokenkey放在请求头中
                //         config.headers.authorization = this.token;
                //         console.log(config.headers.authorization);
                //         //也可以这种写法
                //          // config.headers['accessToken'] = Token;
                //         return config;
                //     }
                //     return config;
                // }, function (error) {
                //     // Do something with request error
                //     return Promise.reject(error);
                // });
                axios.defaults.withCredentials = true;// 允许跨域携带cookie


            },
            methods: {
                onSubmit(values) {
                    console.log(values);
                    axios.post("http://localhost:9090/auth/login", {
                        "username": values.username,
                        "password": values.password
                    }).then(res => {
                        stoken = res.data.token;
                        tokenHead=res.data.tokenHead;
                        console.log(res);
                        this.token = tokenHead+stoken;
                        console.log(this.token);
                    })
                    // axios.get("http://localhost:8080/admin/xiaohie").then(res =>{
                    //     console.log(res);
                    // })
                },
                api() {
                    // axios.get("http://localhost:8080/admin/xiaohie").then(res => {
                    //     console.log(res);

                    // }).catch(res => {
                    //     console.log(res);
                    // })
                    axios({
                        headers:{
                            'authorization':this.token
                        },
                        method:'get',
                        url:'http://localhost:9090/admin/xiaohei'
                    }).then(res =>{
                        console.log(res);
                    })
                },
                xiaohei(){
                    axios({
                        headers:{
                            'authorization':this.token
                        },
                        method:'post',
                        url:'http://localhost:9090/user/user'
                    }).then(res =>{
                        console.log(res);
                    })
                }
            }

        });

        // 调用函数组件,弹出一个 Toast

        // 通过 CDN 引入时不会自动注册 Lazyload 组件
        // 可以通过下面的方式手动注册
        Vue.use(vant.Form);
       // Vue.use(vant.Lazyload);
        //Vue.use(vant.Field)
       // Vue.use(vant.Uploader);
    </script>
</body>

</html>
举报

相关推荐

0 条评论