Springboot2.0+security+jwt 实现权限管理及角色控制(亲测)

本文详细介绍了如何使用SpringBoot、MyBatis、MySQL、Security和JWT搭建一个后台管理系统,涵盖项目创建、依赖配置、权限控制、JWT生成与验证、拦截器配置、Redis缓存管理及用户验证流程。

最近搭建了一个后台管理系统,基于要求使用的是:Springboot+mybatis+mysql+security+jwt;

security :   作为权限控制框架,可以根据不同的角色控制不同的权限请求;

jwt         :作为token的管理器,生成,校验token;

1. 创建springboot 项目 

1.采用网页的方式创建,(圈住的网址);然后将生成的项目导入本地即可;

2.自己创建 (本人)

 2.导入依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.security</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>demo</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <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.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

 3.配置文件 

注意:ignored.urls=忽略校验token的路径

server:
  port: 8085
  servlet:
    context-path: /
  tomcat:
    uri-encoding: UTF-8
    max-threads: 1000
    min-spare-threads: 30

spring:
  # 数据源
  datasource:
    url: jdbc:log4jdbc:mysql://127.0.0.1:3306/cdm?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
    username: root
    password: root
    type: com.alibaba.druid.pool.DruidDataSource
    driverClassName: net.sf.log4jdbc.sql.jdbcapi.DriverSpy
    # Druid StatViewServlet配置
    druid:
      stat-view-servlet:
        # 默认true 内置监控页面首页/druid/index.html
        enabled: true
        url-pattern: /druid/*
        # 允许清空统计数据
        reset-enable: true
        login-username: oa-cisdom
        login-password: oa-cisdom
        # IP白名单 多个逗号分隔
        allow:
        # IP黑名单
        deny:
  jackson:
    serialization:
      WRITE_DATES_AS_TIMESTAMPS: false

  jpa:
    show-sql: true
    # 自动生成表结构
    generate-ddl: false
    open-in-view: false
    hibernate:
      ddl-auto: none

# 忽略鉴权url
ignored:
  urls:
    - /druid/**
    - /swagger-ui.html
    - /swagger-ui.html/**
    - /swagger-resources/**
    - /webjars/springfox-swagger-ui/**
    - /swagger/**
    - /v2/**
    - /**/*.js
    - /**/*.css
    - /**/*.png
    - /**/*.ico
    - /error
    - /auth/login
    - /mapper/*
    - /name/list

#jwt
jwt:
  header: Authorization
  secret: oa-cisdom
  # token 过期时间 2个小时
  expiration: 7200000
  auth:
    # 授权路径
    path: /login
    # 获取用户信息
    account: /info

4. token工具类 。主要作用是生成token,校验token,获取token中一些信息等;

package com.tools.toolmange.security.utils;

import com.tools.toolmange.security.security.JwtUser;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Clock;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.DefaultClock;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.io.Serializable;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

@Component
public class JwtTokenUtil implements Serializable {

    private static final long serialVersionUID = -8898873260896775007L;
    private Clock clock = DefaultClock.INSTANCE;

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private Long expiration;


    public String getUsernameFromToken(String token) throws Exception {
        return getClaimFromToken(token, Claims::getSubject);
    }
    public String getAudienceFromToken(String token) throws Exception {
        return getClaimFromToken(token, Claims::getAudience);
    }

    public Date getIssuedAtDateFromToken(String token) throws Exception {
        return getClaimFromToken(token, Claims::getIssuedAt);
    }

    public Date getExpirationDateFromToken(String token) throws Exception {
        return getClaimFromToken(token, Claims::getExpiration);
    }

    public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) throws Exception {
        final Claims claims = getAllClaimsFromToken(token);
        return claimsResolver.apply(claims);
    }

    private Claims getAllClaimsFromToken(String token) throws Exception {
        return Jwts.parser()
                .setSigningKey(secret)
                .parseClaimsJws(token)
                .getBody();
    }

    public String generateToken(JwtUser userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return doGenerateToken(claims, userDetails);
    }

    private String doGenerateToken(Map<String, Object> claims, JwtUser userDetails) {
        final Date createdDate = clock.now();
        final Date expirationDate = calculateExpirationDate(createdDate);
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(userDetails.getUsername())
                .setIssuedAt(createdDate)
                .setAudience(userDetails.getRemoteIp())
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, secret)
                .compact();
    }

    //防止token过期  7天token失效  服务器不连续一小时操作即刻失效
    private Date calculateExpirationDate(Date createdDate) {
        return new Date(createdDate.getTime() + 604800000);
    }


    public boolean validateToken(String authToken, JwtUser userDetails) throws Exception {
        final Date created = getIssuedAtDateFromToken(authToken);
        return (!isTokenExpired(authToken)
                && !isCreatedBeforeUpdatedReset(created, userDetails.getCreateTime())
        );
    }

    private Boolean isTokenExpired(String token) throws Exception {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(clock.now());
    }

    private boolean isCreatedBeforeUpdatedReset(Date created, Date updateTime) {
        return (updateTime != null && created.before(updateTime));
    }
}

5. 忽略路径读取类,配置拦截器,验证类

读取配置文件中设置的那些忽略路径,加载到缓存中,方便后面使用;此处配置主要是配置跨域及静态资源的过滤

package com.tools.toolmange.common.ignore;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;

@Data
@Configuration
@Component
@ConfigurationProperties(prefix = "ignored")
public class IgnoredUrlsProperties {

    private List<String> urls = Arrays.asList("/resources/)");

}

 校验拦截器

package com.tools.toolmange.common.interceptor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/*
 * @功能描述:校驗接口
 * @作者:lr
 * @时间:2019/8/6
 * @备注:
 */
@Slf4j
@Component
public class AuthorizationInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        //打印请求参数
        return super.preHandle(request, response, handler);
    }

}

 配置拦截器,设置拦截的路径及忽略的路径 (捎带着解决跨域问题)

package com.tools.toolmange.common.interceptor;

import com.tools.toolmange.common.ignore.IgnoredUrlsProperties;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;

import javax.annotation.Resource;

@Configuration
@EnableWebMvc
@Log4j2
public class InterceptorConfiguration implements WebMvcConfigurer {

    @Value("${image_url}")
    private String imageUrl;

    @Resource
    private AuthorizationInterceptor authorizationInterceptor;
    @Autowired
    private IgnoredUrlsProperties ignoredUrlsProperties;

    //解决跨域问题配置
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
                .allowCredentials(true)
                .allowedHeaders("*")
                .allowedOrigins("*")
                .allowedMethods("GET","POST","PUT","DELETE");

    }

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/**").addResourceLocations("classpath:/resources/",imageUrl).setCachePeriod(0);
}

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 注册拦截器
        InterceptorRegistration ir = registry.addInterceptor(authorizationInterceptor);
        // 配置拦截的路径
        ir.addPathPatterns("/**");
        // 配置不拦截的路径
        ir.excludePathPatterns(ignoredUrlsProperties.getUrls());
    }

}

5. SecurityContextHolder,获取用户登录的缓存信息,缓存在security的缓存中,具体可参见security缓存策略介绍,方便以后获取用户信息使用;

package com.tools.toolmange.common.contextholder;

import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.UserDetails;

public class SecurityContextHolder {

    public static UserDetails getUserDetails() {
        UserDetails userDetails = null;
        try {
            userDetails = (UserDetails) org.springframework.security.core.context.SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        } catch (Exception e) {
            throw new BadCredentialsException("登录状态过期");
        }
        return userDetails;
    }
}

6.  security核心配置

package com.tools.toolmange.security.config;

import com.tools.toolmange.common.ignore.IgnoredUrlsProperties;
import com.tools.toolmange.security.security.JwtAccessDeniedHandler;
import com.tools.toolmange.security.security.JwtAuthenticationEntryPoint;
import com.tools.toolmange.security.security.JwtAuthorizationTokenFilter;
import com.tools.toolmange.security.security.JwtLogoutHandler;
import com.tools.toolmange.security.service.JwtUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
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.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.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
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;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;

import javax.annotation.Resource;

/*
 * @功能描述:配置全局校验逻辑   拦截器拦截请求
 * @作者:lr
 * @时间:2019/8/6
 * @备注:
 */
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private IgnoredUrlsProperties ignoredUrlsProperties;

    @Resource
    private JwtAuthenticationEntryPoint authenticationEntryPoint;

    @Resource
    private JwtAccessDeniedHandler authAccessDeniedHandler;

    @Autowired
    private JwtAuthorizationTokenFilter authenticationRequestFilter;

    @Autowired
    private JwtUserDetailsService userDetailsService;

    @Autowired
    private JwtLogoutHandler jwtLogoutHandler;

    @Value("${jwt.auth.path}")
    private String loginPath;

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

    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService)
                .passwordEncoder(passwordEncoderBean());
    }

    @Bean
    GrantedAuthorityDefaults grantedAuthorityDefaults() {
        // Remove the ROLE_ prefix  角色无前缀
        return new GrantedAuthorityDefaults("");
    }

    @Bean
    public HttpFirewall allowUrlEncodedSlashHttpFirewall() {
        StrictHttpFirewall firewall = new StrictHttpFirewall();
        firewall.setAllowUrlEncodedSlash(true);
        return firewall;
    }

    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {

        //忽略路径
        for (String url : ignoredUrlsProperties.getUrls()) {
            httpSecurity.authorizeRequests().antMatchers(url).permitAll();
        }

        httpSecurity.csrf().disable()
                .httpBasic().authenticationEntryPoint(authenticationEntryPoint)
                //session状态  无状态
                .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                //请求之前的认证过滤器  token校验
                .and().authorizeRequests()
                .antMatchers("/jpa/**").permitAll()
                // 所有请求都需要认证
                .anyRequest().authenticated().and().cors();


        httpSecurity
                .headers()
                .frameOptions().sameOrigin()  // required to set for H2 else H2 Console will be blank.
                .cacheControl();

        //httpSecurity.authorizeRequests().requestMatchers(CorsUtils::isPreFlightRequest).permitAll();
        httpSecurity.exceptionHandling().accessDeniedHandler(authAccessDeniedHandler);
        httpSecurity.addFilterBefore(authenticationRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }

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

 7.JwtAccessDeniedHandler,JwtAuthenticationEntryPoint

package com.tools.toolmange.security.security;

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;
import java.io.Serializable;

@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler, Serializable {

    private static final long serialVersionUID = -1846494756214482215L;

    @Override
    public void handle(HttpServletRequest request,
                       HttpServletResponse response,
                       AccessDeniedException authException) throws IOException, ServletException {
        /**
         *  用户已经通过了认证,在访问一个受保护的资源,但是权限不够,那么抛出异常
         */
        response.sendError(605, authException==null?"Unauthorized":authException.getMessage());

    }

}
package com.tools.toolmange.security.security;

import lombok.extern.log4j.Log4j2;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

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

@Component
@Log4j2
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint, Serializable {

    @Override
    public void commence(HttpServletRequest request,
                         HttpServletResponse response,
                         AuthenticationException authException) throws IOException {
        log.error(authException == null ? "Unauthorized" : authException.getMessage());
        //当用户请求了一个受保护的资源,但是用户没有通过认证,那么抛出异常
        response.sendError(403, authException == null ? "Unauthorized" :authException.getMessage());
    }
}

 8. jwtUser  UserDetails的子类

package com.tools.toolmange.security.security;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.Collection;
import java.util.Date;
import java.util.Objects;
import java.util.stream.Collectors;

/*
 * @功能描述:security实体
 * @作者:lr
 * @时间:2019/8/12
 * @备注:
 */
@Data
@AllArgsConstructor
public class JwtUser implements UserDetails {

    @JsonIgnore
    private final int id;

    private final String username;

    @JsonIgnore
    private final String password;

    private final String name;

    @JsonIgnore
    private final boolean enabled;

    private final String icon;

    @JsonIgnore
    private Date createTime;

    @JsonIgnore
    private String remoteIp;

    @JsonIgnore
    private Collection<GrantedAuthority> authorities;

    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return enabled;
    }

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


    public Collection getRoles() {
        return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet());
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        JwtUser jwtUser = (JwtUser) o;
        return enabled == jwtUser.enabled &&
                Objects.equals(username, jwtUser.username);
    }

    @Override
    public int hashCode() {
        return Objects.hash(username, enabled);
    }
}

9.token 过滤类

package com.tools.toolmange.security.security;

import com.alibaba.fastjson.JSONObject;
import com.tools.toolmange.redis.RedisUtil;
import com.tools.toolmange.security.utils.JwtTokenUtil;
import com.tools.toolmange.constant.ConfigConstant;
import com.tools.toolmange.utils.ParamUtil;
import lombok.extern.log4j.Log4j2;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
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;


@Log4j2
@Component
public class JwtAuthorizationTokenFilter extends OncePerRequestFilter {

    private final UserDetailsService userDetailsService;
    private final JwtTokenUtil jwtTokenUtil;
    private final String tokenHeader;

    @Autowired
    private RedisUtil redisUtil;

    @Value("${jwt.expiration}")
    private Long expiration;

    @Value("${redis.token_name}")
    private String tokenName;

    public JwtAuthorizationTokenFilter(@Qualifier("jwtUserDetailsService") UserDetailsService userDetailsService, JwtTokenUtil jwtTokenUtil, @Value("${jwt.header}") String tokenHeader) {
        this.userDetailsService = userDetailsService;
        this.jwtTokenUtil = jwtTokenUtil;
        this.tokenHeader = tokenHeader;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request,
                                    HttpServletResponse response,
                                    FilterChain chain) throws IOException, ServletException {

        final String requestHeader = request.getHeader(this.tokenHeader);

        String username = null;
        String authToken = null;

        JSONObject jon = new JSONObject();
        jon.put("timestamp", System.currentTimeMillis());
        jon.put("success", false);

        if (requestHeader != null && requestHeader.startsWith("Bearer-")) {
            authToken = requestHeader.substring(7);
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json; charset=utf-8");

            try {
                //从令牌中 获取用户名
                username = jwtTokenUtil.getUsernameFromToken(authToken);

            } catch (Exception e) {
                log.error(e);
                jon.put("status", ConfigConstant.TOKEN_ERROR_CODE);
                jon.put("message", ConfigConstant.TOKEN_ERROR_MSG);
                response.getWriter().write(jon.toString());
                return;
            }
        }
        //SecurityContextHolder.getContext().getAuthentication() == null
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

            /**
             *  没有必要强制从数据库中加载使用细节。您还可以将信息存储在令牌中并从中读取。由你决定
             */
            JwtUser userDetails = (JwtUser) this.userDetailsService.loadUserByUsername(username);
            if (userDetails == null) {
                jon.put("status", ConfigConstant.TOKEN_ERROR_CODE);
                jon.put("message", ConfigConstant.USER_ERROR_MSG4);
                response.getWriter().write(jon.toString());
                return;
            }

            /**
             *  对于简单的验证,只检查令牌完整性就足够了。您不必强制调用数据库。还是由你决定
             */
            try {
                //b = jwtTokenUtil.validateToken(authToken, userDetails);

                //获取redis中的token
                String rToken = ObjectUtils.toString(redisUtil.get(username + tokenName), "");

                if (StringUtils.isBlank(rToken)) {
                    jon.put("status", ConfigConstant.TOKEN_ERROR_CODE);
                    jon.put("message", ConfigConstant.TOKEN_ERROR_MSG);
                    response.getWriter().write(jon.toString());
                    return;
                }

                if (!authToken.equals(rToken)) {
                    log.info(rToken);
                    jon.put("status", ConfigConstant.TOKEN_ERROR_CODE);
                    jon.put("message", ConfigConstant.TOKEN_OTHER_MSG);
                    response.getWriter().write(jon.toString());
                    return;
                } else {
                    redisUtil.set(username + tokenName, authToken, expiration);
                }
            } catch (Exception e) {
                jon.put("status", ConfigConstant.TOKEN_ERROR_CODE);
                jon.put("message", e.getMessage());
                response.getWriter().write(jon.toString());
                return;
            }

            UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(authentication);
        }
        jon.clear();
        chain.doFilter(request, response);
    }
}

10.获取用户信息及角色的类

package com.tools.toolmange.security.service;

import com.tools.toolmange.bin.UserBO;
import com.tools.toolmange.dao.omis.UserMapper;
import com.tools.toolmange.security.security.JwtUser;
import org.apache.commons.lang.ObjectUtils;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.annotation.Resource;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;

/*
 * @功能描述:用户验证信息
 * @作者:lr
 * @时间:2019/8/7
 * @备注:
 */
@Service
public class JwtUserDetailsService implements UserDetailsService {

    @Resource
    private UserMapper userMapper;

    private final static String ADMIN_ID = "ADMIN";

    private final static String ADMIN_PERMISSION = "ADMIN";

    @Override
    public UserDetails loadUserByUsername(String userCode) throws UsernameNotFoundException {
        UserBO userBO = userMapper.queryUserInfo(userCode);
        if (userBO == null) {
            return null;
        } else {
            return createJwtUser(userBO);
        }
    }

    private UserDetails createJwtUser(UserBO userBO){

        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        try{
            Date date = sdf.parse(ObjectUtils.toString(userBO.getCreateTime()));
            return new JwtUser(
                    userBO.getId(),
                    userBO.getUserCode(),
                    userBO.getPassWord(),
                    userBO.getNickName(),
                    true,
                    userBO.getUserIcon(),
                    date,
                    "",
                    mapToGrantedAuthorities(userBO)
            );
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    private Collection<GrantedAuthority> mapToGrantedAuthorities(UserBO userBO) {
        List<String> permissions = new ArrayList<>();
        String userCode = userBO.getUserCode();
        if(ADMIN_ID.equalsIgnoreCase(userCode)){
            permissions.add(ADMIN_PERMISSION);
        }else {
            Map<String, Object> params = new HashMap<>();
            params.put("id",userBO.getId());
            permissions = userMapper.findPermissionByPosition(params);
        }
        return permissions.stream()
                .map(permission -> new SimpleGrantedAuthority(permission))
                .collect(Collectors.toList());
    }
}

 11.Redis 配置及工具类

package com.tools.toolmange.redis;


import com.alibaba.fastjson.support.spring.FastJsonRedisSerializer;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.data.redis.RedisProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig {


    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        //使用fastjson序列化
        FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
        // value值的序列化采用fastJsonRedisSerializer
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        // key的序列化采用StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    @Bean
    @ConditionalOnMissingBean(StringRedisTemplate.class)
    public StringRedisTemplate stringRedisTemplate(
            RedisConnectionFactory redisConnectionFactory) {
        StringRedisTemplate template = new StringRedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

}

 

package com.tools.toolmange.redis;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

@Component
public final class RedisUtil {

    @Resource
    private RedisTemplate<String, Object> redisTemplate;


    public Set<String> keys(String keys){
        try {
            return redisTemplate.keys(keys);
        }catch (Exception e){
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 指定缓存失效时间
     * @param key 键
     * @param time 时间(秒)
     * @return
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 根据key 获取过期时间
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }
    /**
     * 判断key是否存在
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 删除缓存
     * @param key 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }
    /**
     * 普通缓存获取
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }


    /**
     * 普通缓存放入
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 普通缓存放入, 不存在放入,存在返回
     * @param key 键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean setnx(String key, Object value) {
        try {
            redisTemplate.opsForValue().setIfAbsent(key,value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 普通缓存放入并设置时间
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间,不存在放入,存在返回
     * @param key 键
     * @param value 值
     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean setnx(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 递增
     * @param key 键
     * @param delta 要增加几(大于0)
     * @return
     */
    public long incr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递增因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, delta);
    }
    /**
     * 递减
     * @param key 键
     * @param delta 要减少几(小于0)
     * @return
     */
    public long decr(String key, long delta) {
        if (delta < 0) {
            throw new RuntimeException("递减因子必须大于0");
        }
        return redisTemplate.opsForValue().increment(key, -delta);
    }
    /**
     * HashGet
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }
    /**
     * 获取hashKey对应的所有键值
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);
    }
    /**
     * HashSet
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * HashSet 并设置时间
     * @param key 键
     * @param map 对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 向一张hash表中放入数据,如果不存在将创建
     * @param key 键
     * @param item 项
     * @param value 值
     * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 删除hash表中的值
     * @param key 键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }
    /**
     * 判断hash表中是否有该项的值
     * @param key 键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }
    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     * @param key 键
     * @param item 项
     * @param by 要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }
    /**
     * hash递减
     * @param key 键
     * @param item 项
     * @param by 要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, -by);
    }
    /**
     * 根据key获取Set中的所有值
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 根据value从一个set中查询,是否存在
     * @param key 键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将数据放入set缓存
     * @param key 键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 将set数据放入缓存
     * @param key 键
     * @param time 时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0)
                expire(key, time);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 获取set缓存的长度
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 移除值为value的
     * @param key 键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    // ===============================list=================================
    /**
     * 获取list缓存的内容
     * @param key 键
     * @param start 开始
     * @param end 结束 0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 获取list缓存的长度
     * @param key 键
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
    /**
     * 通过索引 获取list中的值
     * @param key 键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     * @param key 键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 将list放入缓存
     *
     * @param key 键
     * @param value 值
     * @param time 时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0)
                expire(key, time);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 根据索引修改list中的某条数据
     * @param key 键
     * @param index 索引
     * @param value 值
     * @return
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
    /**
     * 移除N个值为value
     * @param key 键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        try {
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }
}

12.测试的controller 及  service

package com.tools.toolmange.security.controller;

import com.tools.toolmange.security.service.AuthenticationService;
import com.tools.toolmange.constant.ConfigConstant;
import com.tools.toolmange.utils.ParamUtil;
import com.tools.toolmange.utils.ResultUtil;
import com.tools.toolmange.bin.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping("auth")
public class AuthenticationController {

    @Resource
    private AuthenticationService authenticationService;

    @RequestMapping(value = "${jwt.auth.path}")
    public Result login(@RequestBody Map<String, Object> params,HttpServletRequest request) {

        ResultUtil rt = new ResultUtil();
        //校验参数
        Map<String, String> checkParams = new HashMap<>();
        checkParams.put("userCode", "用户名");
        checkParams.put("passWord", "密码");
        String result = ParamUtil.checkMapParam(params, checkParams);
        if (StringUtils.isNotEmpty(result)) {
            return rt.setErrorMsg(ConfigConstant.SYSTEM_ERROR_CODE, result);
        }
        params.put("remoteIp",ParamUtil.getIpAddress(request));//获取用户请求ip校验ip和token
        return authenticationService.authentication(params);

    }
}

 

package com.tools.toolmange.security.service;

import com.tools.toolmange.redis.RedisUtil;
import com.tools.toolmange.security.security.AuthenticationInfo;
import com.tools.toolmange.security.security.JwtUser;
import com.tools.toolmange.security.utils.JwtTokenUtil;
import com.tools.toolmange.utils.Base64Util;
import com.tools.toolmange.constant.ConfigConstant;
import com.tools.toolmange.utils.ResultUtil;
import com.tools.toolmange.bin.Result;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.ObjectUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.Map;

/*
 * @功能描述:
 * @作者:lr
 * @时间:2019/9/11
 * @备注:
 */
@Slf4j
@Service
public class AuthenticationService {

    @Resource
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    @Qualifier("jwtUserDetailsService")//唯一的实现限定
    private UserDetailsService userDetailsService;

    @Value("${jwt.expiration}")
    private Long expiration;

    @Value("${redis.token_name}")
    private String tokenName;

    /*
     * @功能描述:登录逻辑
     * @作者:lr
     * @时间:2019/9/11
     * @备注:
     */
    public Result authentication(Map<String, Object> params) {

        ResultUtil rt = new ResultUtil();
        try {
            //查询用户信息
            final JwtUser authUser =
                    (JwtUser) userDetailsService.loadUserByUsername(ObjectUtils.toString(params.get("userCode")));
            if (authUser == null)
                return rt.setErrorMsg(ConfigConstant.USER_ERROR_CODE, ConfigConstant.USER_ERROR_MSG);
            if (!authUser.getPassword().equals(ObjectUtils.toString(Base64Util.Encode(ObjectUtils.toString(params.get("passWord"))))))
                return rt.setErrorMsg(ConfigConstant.USER_ERROR_CODE, ConfigConstant.USER_ERROR_MSG);
            //放入ip
            String remoteIp = ObjectUtils.toString(params.get("remoteIp"));
            authUser.setRemoteIp(remoteIp);
            //获取token信息
            final String token = jwtTokenUtil.generateToken(authUser);
            //存储token 有效期2小时
            redisUtil.set(authUser.getUsername() + tokenName, token, expiration);
            return rt.setData(new AuthenticationInfo(token, authUser));
        } catch (Exception e) {
            e.printStackTrace();
            return rt.setErrorCodeMsg(ConfigConstant.ERROR_MSG);
        }
    }
}

 13.数据库 :用户表,角色表,菜单表

 

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值