Spring Security配置

一、添加依赖

  <!-- mybatis-plus -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>

        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId> <optional>true</optional>
        </dependency>

        <!-- mysql-connector -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.32</version>
        </dependency>

        <dependency>
            <groupId>com.nimbusds</groupId>
            <artifactId>nimbus-jose-jwt</artifactId>
            <version>9.11.1</version>
        </dependency>

        <!--json-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.25</version>
        </dependency>

        <!-- springboot整合redis-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!-- 使用 lettuce 时要加这个包;使用 jedis 时则不需要。-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

二、创建四层架构

1.SecurityApplication(主方法)

package com.security;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
@MapperScan("com.security.mapper")//扫描mapper层
public class SecurityApplication {

    public static void main(String[] args) {
        SpringApplication.run(SecurityApplication.class, args);
    }
}


2.JWTUtil(工具类)

package com.security.util;

import com.nimbusds.jose.*;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import org.springframework.stereotype.Component;

import java.text.ParseException;
import java.util.Map;

/**
 * jwt 工具类
 * 1: 创建jwt
 * 2:解密jwt
 */
@Component
public class JWTUtil {
    private static final String KEY="ksdjfvhwhfowsrhfgdfhgdrygheqweqweqeqweqwreafwseefgwetgffgwsdfgwgweftgsdgdrhg";


    public static String createJWT(Map map) throws JOSEException {
        //第一部分 头部,主要防jwt自我描述部分,比如加密方式
        JWSHeader header=new JWSHeader.Builder(JWSAlgorithm.HS256)
                .type(JOSEObjectType.JWT).build();

        //第二部分 载荷部分,主要放用户登录成功后,一些个人信息(注意:不要防敏感信息)
        Payload payload=new Payload(map);

        //第三部分 签名部分,(头部+载荷)通过密钥加密后得到的
        JWSObject jwsObject=new JWSObject(header,payload);
        JWSSigner jwsSigner=new MACSigner(KEY);

        //拿到密钥加密
        jwsObject.sign(jwsSigner);
        return jwsObject.serialize();
    }


    /**
     * 拿到jwt 根据系统密钥,看能不能解开
     * @return
     */
    public static boolean decode(String jwt) throws ParseException, JOSEException {
        //parse() 把字符串转成一个对象
        JWSObject jwsObject=JWSObject.parse(jwt);
        JWSVerifier jwsVerifier=new MACVerifier(KEY);
        //解密方法 verify()
        return jwsObject.verify(jwsVerifier);
    }

    /**
     * 根据jwt 获取其中载荷部分
     * @param jwt
     * @return
     */
    public static Map getPayLoad(String jwt) throws ParseException {
        //parse() 把字符串转成一个对象,并解密
        JWSObject jwsObject=JWSObject.parse(jwt);
        Payload payload=jwsObject.getPayload();
        Map<String,Object> map=payload.toJSONObject();
        return map;
    }
}

二、创建实体类与枚举

1.Users

用户信息实体类

package com.security.entity;

import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
import lombok.Data;

import java.util.List;

@Data
public class Users {
    @JsonSerialize(using = ToStringSerializer.class)//防止json传过来导致精度缺失
    private Long id;
    private String userName;
    private String account;
    private String password;
    private List<String> anths;//用户权限
}

2.ResponseEnum

枚举HttpResult层中所用静态方法

package com.security.entity;

public enum ResponseEnum {
    LOGIN_SUCCESS(200,"OK"),
    LOGIN_FAIL(500,"NO"),
    NO_LOGIN(501,"NO_LOGIN"),
    NO_AUTH(502,"NO_AUTH")
    ;
    private Integer code;
    private String msg;

    ResponseEnum() {
    }

    ResponseEnum(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

3.HttpResult

HttpResult是后端向前端传输数据时所用实体类

package com.security.entity;

import lombok.Data;
import lombok.NoArgsConstructor;


@Data
@NoArgsConstructor
public class HttpResult<T> {
    private Integer code;
    private String msg;
    private T data;

    public HttpResult<T> ok(T t){
        return new HttpResult(ResponseEnum.LOGIN_SUCCESS.getCode(), t);
    }

    public static final HttpResult<Void> LOGIN_SUCCESS=
            new HttpResult(ResponseEnum.LOGIN_SUCCESS.getCode(),
                    ResponseEnum.LOGIN_SUCCESS.getMsg());

    public static final HttpResult<Void> LOGIN_FAIL=
            new HttpResult(ResponseEnum.LOGIN_FAIL.getCode(),
                    ResponseEnum.LOGIN_FAIL.getMsg());

    public static final HttpResult<Void> NO_LOGIN=
            new HttpResult(ResponseEnum.NO_LOGIN.getCode(),
                    ResponseEnum.NO_LOGIN.getMsg());

    public static final HttpResult<Void> NO_AUTH=
            new HttpResult(ResponseEnum.NO_AUTH.getCode(),
                    ResponseEnum.NO_AUTH.getMsg());

    public HttpResult(Integer code, T data) {
        this.code = code;
        this.data = data;
    }
    public HttpResult(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }
}

三、配置.yml文件

server:
  port: 8080
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/2_27user?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: 123456

mybatis:
  mapper-locations: classpath:mapper/*.xml

logging:
  level:
    com.woniu.dao: debug
  pattern:
    console: '%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n'

四、配置.xml文件(维护SQL语句)

创建getUserInfoByAccount方法,通过account获取用户信息

<?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.security.mapper.UserMapper">

    <resultMap id="userMap" type="com.security.entity.Users">
        <id property="id" column="id"></id>
        <result property="userName" column="user_name"></result>
        <result property="account" column="account"></result>
        <result property="password" column="password"></result>
        <collection property="anths" ofType="java.lang.String">
            <result column="anth_code"></result>
        </collection>
    </resultMap>
    <select id="getUserInfoByAccount" resultMap="userMap">
        select
            us.id,
            us.user_name,
            us.account,
            us.password,
            ta.anth_code
        from
            t_user us
                left join user_anth ua on us.id=ua.user_id
                left join t_anth ta on ua.anth_id=ta.id
        where account =#{account}
    </select>
</mapper>

ps:数据库:

t_user(用户信息表)

t_anth (角色表)

user_anth(用户角色关系表)

五、mapper与service层

1.UserMapper

package com.security.mapper;

import com.security.entity.Users;
import org.springframework.stereotype.Repository;

@Repository
public interface UserMapper {
    /**
     * 根据账号查看用户信息及其权限
     * @param account
     * @return
     */
    Users getUserInfoByAccount(String account);
}

2.SecurityService

package com.security.service;

import com.security.entity.Users;
import com.security.mapper.UserMapper;
import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.security.core.authority.AuthorityUtils;
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.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

@Service
public class SecurityService implements UserDetailsService {
    @Autowired
    private PasswordEncoder passwordEncoder;
    @Autowired
    private UserMapper userMapper;

    /**
     * username:页面传过来的username
     * @param username
     * @return
     * @throws UsernameNotFoundException
     */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

        Users users=userMapper.getUserInfoByAccount(username);
        if(users!=null) {
            String anths=String.join(",",users.getAnths());
            //username 数据库产查用户信息
            return new User(users.getAccount(), passwordEncoder.encode(users.getPassword()),
                    AuthorityUtils.commaSeparatedStringToAuthorityList(anths));
        }
        else {
            throw new UsernameNotFoundException("该用户不存在");
        }
    }
}

六、创建handler(处理器)与config(配置)层

七、配置handler(处理器)层

1.LoginFailHandler(登录失败处理器)

package com.security.handler;

import com.alibaba.fastjson.JSON;
import com.security.entity.HttpResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

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


public class LoginFailHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest,
                                        HttpServletResponse httpServletResponse,
                                        AuthenticationException e)
            throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(HttpResult.LOGIN_FAIL));
    }
}

2.LoginSuccessHandler(登录成功处理器)

package com.security.handler;

import com.alibaba.fastjson.JSON;
import com.security.entity.HttpResult;

import com.security.util.JWTUtil;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
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;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 登录成功处理器
 */
@Component
public class LoginSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @SneakyThrows//注解抛异常
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest,
                                        HttpServletResponse httpServletResponse,
                                        Authentication authentication)
            throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");

        User user=(User)authentication.getPrincipal();
        String username=user.getUsername();
        Map map=new HashMap();
        map.put("username",username);
        String jwt=JWTUtil.createJWT(map);
        //拿jwt干啥:1、放到redis,2、把jwt传到前端
        redisTemplate.opsForValue().set("jwt:"+username,jwt,30, TimeUnit.MINUTES);


        httpServletResponse.getWriter().write(JSON.toJSONString(new HttpResult().ok(jwt)));
    }
}

3.NoLoginHandler(未登录处理器)

package com.security.handler;

import com.alibaba.fastjson.JSON;
import com.security.entity.HttpResult;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;

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

/**
 * 用户未登录:访问系统接口
 */
public class NoLoginHandler implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest,
                         HttpServletResponse httpServletResponse,
                         AuthenticationException e)
            throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(HttpResult.NO_LOGIN));
    }
}

4.NoAuthHandler(无权限处理器)

package com.security.handler;

import com.alibaba.fastjson.JSON;
import com.security.entity.HttpResult;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;

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

public class NoAuthHandler implements AccessDeniedHandler {
    @Override
    public void handle(HttpServletRequest httpServletRequest,
                       HttpServletResponse httpServletResponse,
                       AccessDeniedException e)
            throws IOException, ServletException {
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(HttpResult.NO_AUTH));

    }
}

5.JWTFilter(jwt过滤器)

package com.security.handler;

import com.security.config.SecurityConfig;
import com.security.service.SecurityService;
import com.security.util.JWTUtil;
import lombok.SneakyThrows;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
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;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 检验jwt
 * 1、判断请求头是否携带jwt
 *   否:放行不处理
 *   是:走到第二步
 * 2、对前端传过来的jwt解密
 *   否:放行不处理
 *   是:走到第三步
 * 3、获取Redis的jwt
 *   获取不到:放行不处理
 *   获取到:走到第四步
 * 4、对比jwt
 *   否:放行不处理
 *   是:走到第五步
 * 5、给jwt续期
 */
@Component
public class JWTFilter extends OncePerRequestFilter {
    @Autowired
    private SecurityService securityService;
    /**
     * StringRedisTemplate和RedisTemplate
     */
    @Autowired
    private StringRedisTemplate redisTemplate;
    @SneakyThrows
    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest,
                                    HttpServletResponse httpServletResponse,
                                    FilterChain filterChain)
            throws ServletException, IOException {
        /**
         *  * 1、判断请求头是否携带jwt
         *  *   否:放行不处理
         *  *   是:走到第二步
         */
        String jwt=httpServletRequest.getHeader("jwt");
        if(jwt==null){
            filterChain.doFilter(httpServletRequest,httpServletResponse);//交给过滤器处理
            return;
        }
        /**
         *  * 2、对前端传过来的jwt解密
         *  *   否:放行不处理
         *  *   是:走到第三步
         */
        if(!JWTUtil.decode(jwt)){
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }
        /**
         *  * 3、获取Redis的jwt
         *  *   获取不到:放行不处理
         *  *   获取到:走到第四步
         */
        Map payLoad=JWTUtil.getPayLoad(jwt);
        String username=(String) payLoad.get("username");
        String redisJwt=redisTemplate.opsForValue().get("jwt:"+payLoad.get("username"));
        if(redisJwt==null){
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }
        /**
         *  * 4、对比jwt
         *  *   否:放行不处理
         *  *   是:走到第五步
         */
        if(!jwt.equals(redisJwt)){
            filterChain.doFilter(httpServletRequest,httpServletResponse);
            return;
        }
        /**
         * * 5、给jwt续期
         */
        redisTemplate.opsForValue().set("jwt:"+payLoad.get("username"),jwt,30, TimeUnit.MINUTES);

        //把用户信息放到security容器中
        UserDetails userDetails=securityService.loadUserByUsername(username);
        UsernamePasswordAuthenticationToken upa=new UsernamePasswordAuthenticationToken( userDetails.getUsername(),
                                                                                         userDetails.getPassword(),
                                                                                         userDetails.getAuthorities());
        //把信息放到security容器中
        SecurityContextHolder.getContext().setAuthentication(upa);
        filterChain.doFilter(httpServletRequest,httpServletResponse);
    }
}

八、配置congfig层

1.CorsConfig(处理跨域申请)

package com.security.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class CorsConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addCorsMappings(CorsRegistry registry){
        // 设置允许跨域的路径
        registry.addMapping("/**")
                // 设置允许跨域请求的域名
                .allowedOriginPatterns("*")
                // 是否允许cookie
                .allowCredentials(false)
                // 设置允许的请求方式
                .allowedMethods("GET", "POST", "DELETE", "PUT")
                // 设置允许的header属性
                .allowedHeaders("*")
                // 跨域允许时间
                .maxAge(3600);
    }
}

2.SecurityConfig(Spring Security配置文件)

package com.security.config;

import com.security.handler.*;
import com.security.service.SecurityService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;

import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@EnableWebSecurity
@EnableGlobalMethodSecurity(securedEnabled = true,jsr250Enabled = true,prePostEnabled = true) //作用:自动开启注解式授权
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JWTFilter jWTFilter;
    @Autowired
    private LoginSuccessHandler loginSuccessHandler;
    @Autowired
    private SecurityService securityService;

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(securityService);
    }

    /**
     * 自定义登录页面
     * @param http
     * @throws Exception
     */
    public void configure(HttpSecurity http) throws Exception {
        http.formLogin() //告诉框架自定义页面
               .loginPage("/login.html") //登录页面地址
               .loginProcessingUrl("/dologin") //对应表单提交的action
               .successHandler(loginSuccessHandler)
               .failureHandler(new LoginFailHandler())
               .permitAll();//对login.html和dologin请求放行
        http.exceptionHandling()
                .accessDeniedHandler(new NoAuthHandler())
                .authenticationEntryPoint(new NoLoginHandler());
        http.authorizeRequests()
//                .antMatchers("/hello").hasAuthority("anth_techer") //有anth_techer的权限才能访问/hello
//                .antMatchers("/hello").hasAnyAuthority("anth_techer","hello") //有anth_techer或hello的权限才能访问/hello
//                .antMatchers("/hello").hasRole("anth_techer") //会在anth_techer加ROLE_前缀
//                .antMatchers("/hello").permitAll() //配置免登陆接口
                .anyRequest().authenticated(); //所有请求都拦截
        /**
         * 把jwtfilter注入进来
         */
        http.addFilterAfter(jWTFilter, UsernamePasswordAuthenticationFilter.class);
        /**
         * 把session禁掉
         */
        http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        //防跨站脚本攻击关闭
         http.csrf().disable();
         //运行跨域
         http.cors();
    }

    /**
     * 数据加密类
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

ps:配置登录页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>登录</title>
</head>
<body>
<form action="dologin" method="post">
    <!--注意:帐号和密码的名称必须是username和password否则spring security无法识别-->
    <p>帐号:<input type="text" name="username"></p>
    <p>密码:<input type="text" name="password"></p>
    <p><button type="submit">登录</button></p>
</form>
</body>
</html>

九、配置控制层

1.UserController

package com.security.controller;

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

@RestController
public class UserController {
//    @Secured("ROLE_anth_techer") //访问页面所需权限
//    @PermitAll//全部可访问
//    @PreAuthorize("hasAuthority('anth_techer')")
    @RequestMapping("/hello")
    public String hello(){
        return "hello";
    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值