美文网首页java相关
电商项目mall学习(4)整合SpringSecurity和JW

电商项目mall学习(4)整合SpringSecurity和JW

作者: xywh | 来源:发表于2020-06-23 07:02 被阅读0次

    向电商项目中集成安全认证和授权功能

    使用框架

    1.SpringSecurity

    SpringSecurity是一个认证和授权框架,对于Spring来说SpringSecurity是一套web安全标准

    2.JWT

    JWT是Json Web Token的缩写,基于RFC7519标准定义的一种可以安全传输的json对象,由于使用了数字签名,所以是安全和可以信任的

    • JWT的组成
      • JWT token的格式为:

    header.payload.signature

    • header中用于存放签名的生成算法
      • {"alg": "HS512"}
    • payload中用于存放用户名,token的生成时间,过期时间
      • {"sub":"admin","created":"1592831217914","exp":"1593436017"}
    • signature为以header和payload生成的签名,一旦header和payload被篡改,将验证失败
      • HMACSHA256(
        base64UrlEncode(header) + "." +
        base64UrlEncode(payload),secret)
    • JWT实例

    eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ0ZXN0IiwiY3JlYXRlZCI6MTU5MjgzMTIxNzkxNCwiZXhwIjoxNTkzNDM2MDE3fQ.AI1ZFvl7kEYsTlq7b5rdmdaIHMH-_HrZKVzrp5W7zbkpWn2ZXmmC5u0W4rdX8Ziia7yJBm5e9CCetfa83A50kQ

    可以在jwt.io网站上对jwt实例进行解析https://jwt.io/

    • JWT的认证和授权原理
      1. 用户调用登录接口,登录成功后获取到JWT的token;
      2. 之后用户每次调用接口都在http的header中添加一个叫Authorization的头,值为JWT的token;
      3. 后台程序通过对Authorization头中信息的解码及数字签名校验来获取其中的用户信息,从而实现认证和授权。
    3.Hutool

    Hutool是一个丰富的Java开源工具包,它帮助我们简化每一行代码,减少每一个方法,mall项目采用了此工具包。


    项目源码地址

    https://github.com/macrozheng/mall-learning/tree/master/mall-tiny-04


    项目使用表说明

    ums_admin:后台用户表
    ums_role:后台用户角色表
    ums_permission:后台用户权限表
    ums_admin_role_relation:后台用户和角色关系表,用户与角色是多对多关系
    ums_role_permission_relation:后台用户角色和权限关系表,角色与权限是多对多关系
    ums_admin_permission_relation:后台用户和权限关系表(除角色中定义的权限以外的加减权限),加权限是指用户比角色多出的权限,减权限是指用户比角色少的权限
    使用MyBatis Generator生成相关的model,mapper,mapper.xml
    修改generatorConfig.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE generatorConfiguration
            PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
            "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    
    <generatorConfiguration>
        <properties resource="generator.properties"/>
        <context id="MySqlContext">
            <property name="beginningDelimiter" value="`"/>
            <property name="endingDelimiter" value="`"/>
            <property name="javaFileEncoding" value="UTF-8"/>
            <!-- 为模型生成序列化方法-->
            <plugin type="org.mybatis.generator.plugins.SerializablePlugin"/>
            <!-- 为生成的Java模型创建一个toString方法 -->
            <plugin type="org.mybatis.generator.plugins.ToStringPlugin"/>
    
            <!--可以自定义生成model的代码注释-->
            <commentGenerator type="com.mall.mallmybatis.mbg.CommentGenerator">
                <!-- 是否去除自动生成的注释 true:是 : false:否 -->
                <property name="suppressAllComments" value="true"/>
                <property name="suppressDate" value="true"/>
                <property name="addRemarkComments" value="true"/>
            </commentGenerator>
    
            <!--配置数据库连接-->
            <jdbcConnection driverClass="${jdbc.driverClass}"
                            connectionURL="${jdbc.connectionURL}"
                            userId="${jdbc.userId}"
                            password="${jdbc.password}">
                <!--解决mysql驱动升级到8.0后不生成指定数据库代码的问题-->
                <property name="nullCatalogMeansCurrent" value="true" />
            </jdbcConnection>
    
            <!--指定生成model的路径-->
            <javaModelGenerator targetPackage="com.mall.mallmybatis.mbg.model" targetProject="mallmybatis\src\main\java"/>
            <!--指定生成mapper.xml的路径-->
            <sqlMapGenerator targetPackage="com.mall.mallmybatis.mbg.mapper" targetProject="mallmybatis\src\main\resources"/>
            <!--指定生成mapper接口的的路径-->
            <javaClientGenerator type="XMLMAPPER" targetPackage="com.mall.mallmybatis.mbg.mapper"
                                 targetProject="mallmybatis\src\main\java"/>
            <!--生成全部表tableName设为%-->
            <table tableName="pms_brand">
                <generatedKey column="id" sqlStatement="MySql" identity="true"/>
            </table>
            <!--后台用户表-->
            <table tableName="ums_admin">
                <generatedKey column="id" sqlStatement="MySql" identity="true"/>
            </table>
            <!--后台用户角色表-->
            <table tableName="ums_role">
                <generatedKey column="id" sqlStatement="MySql" identity="true"/>
            </table>
            <!--后台用户权限表-->
            <table tableName="ums_permission">
                <generatedKey column="id" sqlStatement="MySql" identity="true"/>
            </table>
    <!--        后台用户和角色关系表-->
            <table tableName="ums_admin_role_relation">
                <generatedKey column="id" sqlStatement="MySql" identity="true"/>
            </table>
    <!--        后台用户角色和权限关系表-->
            <table tableName="ums_role_permission_relation">
                <generatedKey column="id" sqlStatement="MySql" identity="true"/>
            </table>
    <!--        后台用户和权限关系表-->
            <table tableName="ums_admin_permission_relation">
                <generatedKey column="id" sqlStatement="MySql" identity="true"/>
            </table>
        </context>
    
    </generatorConfiguration>
    

    执行Generator类,生成相关的数据表对应的类和配置


    SpringBoot中添加SpringSecurity和JWT

      1. 在项目中添加Security,Hutool和JWT的依赖,修改pom.xml文件,修改application.yml
    • pom.xml添加需要引入的SpringSecurity,JWT,Hutool依赖
    <!--SpringSecurity依赖配置-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-security</artifactId>
    </dependency>
    <!--Hutool Java工具包-->
    <dependency>
        <groupId>cn.hutool</groupId>
        <artifactId>hutool-all</artifactId>
        <version>4.5.7</version>
    </dependency>
    <!--JWT(Json Web Token)登录支持-->
    <dependency>
        <groupId>io.jsonwebtoken</groupId>
        <artifactId>jjwt</artifactId>
        <version>0.9.0</version>
    </dependency>
    

    application.yml添加自定义jwt信息

    # 自定义jwt key
    jwt:
      tokenHeader: Authorization #JWT存储的请求头
      secret: mySecret #JWT加解密使用的密钥
      expiration: 604800 #JWT的超期限时间(60*60*24)
      tokenHead: Bearer  #JWT负载中拿到开头
    
      1. 添加JWT token的工具类

    用于生成和解析JWT token的工具类

    相关方法说明
    generateToken(UserDetails userDetails) :用于根据登录用户信息生成token
    getUserNameFromToken(String token):从token中获取登录用户的信息
    validateToken(String token, UserDetails userDetails):判断token是否还有效

    package com.mall.mallmybatis.common.util;
    
    import io.jsonwebtoken.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.security.core.userdetails.UserDetails;
    import org.springframework.stereotype.Component;
    
    import java.util.Date;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @author wangxing
     * @version 2020/6/22 15:23 Administrator
     */
    @Component
    public class JwtTokenUtil {
        private static final Logger LOGGER = LoggerFactory.getLogger(JwtTokenUtil.class);
        private static final String CLAIM_KEY_USERNAME = "sub";
        private static final String CLAIM_KEY_CREATED = "created";
        @Value("${jwt.secret}")
        private String secret;
        @Value("${jwt.expiration}")
        private Long expiration;
    
        /**
         * 生成JWT的token
         */
        private String generateToken(Map<String, Object> claims) {
            return Jwts.builder()
                    .setClaims(claims)
                    .setExpiration(generateExpirationDate())
                    .signWith(SignatureAlgorithm.HS512, secret)
                    .compact();
        }
    
        /**
         * 从token中获取JWT中的负载
         * @param token
         * @return
         */
        private Claims getClaimsFromToken(String token){
            Claims claims = null;
            try {
                claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody();
            } catch (Exception e) {
                LOGGER.error("JWT格式验证失败:{}",token);
            }
            return claims;
        }
    
        /**
         * 生成token的过期时间
         * @return
         */
        private Date generateExpirationDate(){
            return new Date(System.currentTimeMillis()+expiration*1000);
        }
    
        /**
         * 从token中获取登录用户名
         * @param token
         * @return
         */
        public String getUserNameFromToken(String token){
            String username;
            try {
                Claims claims = getClaimsFromToken(token);
                username =  claims.getSubject();
            } catch (Exception e) {
                username = null;
                LOGGER.error("token中没有能正确获取用户名token:{}",token);
            }
            return username;
        }
        /**
         * 验证token是否还有效
         * @param token       客户端传入的token
         * @param userDetails 从数据库中查询出来的用户信息
         */
        public boolean validateToken(String token, UserDetails userDetails) {
            String username = getUserNameFromToken(token);
            return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
        }
        /**
         * 判断token是否已经失效
         */
        private boolean isTokenExpired(String token) {
            Date expiredDate = getExpiredDateFromToken(token);
            return expiredDate.before(new Date());
        }
        /**
         * 从token中获取过期时间
         */
        private Date getExpiredDateFromToken(String token) {
            Claims claims = getClaimsFromToken(token);
            return claims.getExpiration();
        }
        /**
         * 根据用户信息生成token
         */
        public String generateToken(UserDetails userDetails) {
            Map<String, Object> claims = new HashMap<>();
            claims.put(CLAIM_KEY_USERNAME, userDetails.getUsername());
            claims.put(CLAIM_KEY_CREATED, new Date());
            return generateToken(claims);
        }
        /**
         * 判断token是否可以被刷新
         */
        public boolean canRefresh(String token) {
            return !isTokenExpired(token);
        }
        /**
         * 刷新token
         */
        public String refreshToken(String token) {
            Claims claims = getClaimsFromToken(token);
            claims.put(CLAIM_KEY_CREATED, new Date());
            return generateToken(claims);
        }
    }
    
    1. 添加SpringSecurity的配置类
      package com.mall.mallmybatis.config;
    
    import com.mall.mallmybatis.componet.JwtAuthenticationTokenFilter;
    import com.mall.mallmybatis.componet.RestAuthenticationEntryPoint;
    import com.mall.mallmybatis.componet.RestfulAccessDeniedHandler;
    import com.mall.mallmybatis.dto.AdminUserDetails;
    import com.mall.mallmybatis.mbg.model.UmsAdmin;
    import com.mall.mallmybatis.mbg.model.UmsPermission;
    import com.mall.mallmybatis.service.UmsAdminService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.authentication.AuthenticationManager;
    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.core.userdetails.UserDetailsService;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
    
    import java.util.List;
    
    /**
     * @author wangxing
     * @version 2020/6/22 15:49 Administrator
     */
    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {
        @Autowired
        private UmsAdminService adminService;
        /**
         * 当用户没有访问权限时的处理器,用于返回JSON格式的处理结果
         */
        @Autowired
        private RestfulAccessDeniedHandler restfulAccessDeniedHandler;
        /**
         * 当未登录或token失效时,返回JSON格式的结果
         */
        @Autowired
        private RestAuthenticationEntryPoint restAuthenticationEntryPoint;
    
        /**
         * 用于配置需要拦截的url路径、jwt过滤器及出异常后的处理器
         * @param httpSecurity
         * @throws Exception
         */
        @Override
        protected void configure(HttpSecurity httpSecurity) throws Exception {
            httpSecurity.csrf()// 由于使用的是JWT,我们这里不需要csrf
                    .disable()
                    .sessionManagement()// 定制我们自己的 session 策略,
                    .sessionCreationPolicy(SessionCreationPolicy.STATELESS)// 基于token,所以不需要session,调整为让 Spring Security 不创建和使用 session
                    .and()
                    .authorizeRequests() //对基于request访问的限制
                    .antMatchers(HttpMethod.GET, // 允许对于网站静态资源的无授权访问
                            "/",
                            "/*.html",
                            "/favicon.ico",
                            "/**/*.html",
                            "/**/*.css",
                            "/**/*.js",
                            "/swagger-resources/**",
                            "/v2/api-docs/**",
                            "/webjars/springfox-swagger-ui/**"
                    )
                    .permitAll() // 允许所有请求通过
                    .antMatchers(HttpMethod.OPTIONS)//跨域请求会先进行一次options请求
                    .permitAll()
                    .antMatchers("/admin/login", "/admin/register")// 对登录注册要允许匿名访问
                    .permitAll()
                    .antMatchers("/esProduct/**","/member/readHistory/**","/order/**","/aliyun/oss/**","/sso/**")// 测试时放开
                    .permitAll()
                    .anyRequest()// 除上面外的所有请求全部需要鉴权认证
                    .authenticated();//需携带有效 token
            // 禁用缓存
            httpSecurity.headers().cacheControl();
            // 添加JWT filter
            httpSecurity.addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class);
            //添加自定义未授权和未登录结果返回
            httpSecurity.exceptionHandling()
                    .accessDeniedHandler(restfulAccessDeniedHandler)
                    .authenticationEntryPoint(restAuthenticationEntryPoint);
        }
    
        /**
         * 用于配置UserDetailsService及PasswordEncoder
         * @param auth
         * @throws Exception
         */
        @Override
        protected void configure(AuthenticationManagerBuilder auth) throws Exception {
            auth.userDetailsService(userDetailsService())
                    .passwordEncoder(passwordEncoder());
        }
    
        /**
         * PasswordEncoder定义的用于对密码进行编码及比对的接口,目前使用的是BCryptPasswordEncoder
         * @return
         */
        @Bean
        public PasswordEncoder passwordEncoder() {
            return new BCryptPasswordEncoder();
        }
    
        /**
         * 获取登录用户信息
         * @return
         */
        @Bean
        public UserDetailsService userDetailsService() {
            //获取登录用户信息  定义的核心接口,用于根据用户名获取用户信息,自定义实现UserDetailsService接口
            return username -> {
                UmsAdmin admin = adminService.getAdminByUsername(username);
                if (admin != null) {
                    List<UmsPermission> permissionList = adminService.getPermissionList(admin.getId());
                    return new AdminUserDetails(admin,permissionList);
                }
                throw new UsernameNotFoundException("用户名或密码错误");
            };
        }
    
        /**
         * 在用户名和密码校验前添加的过滤器,如果有jwt的token,会自行根据token信息进行登录。
         * @return
         */
        @Bean
        public JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter(){
            return new JwtAuthenticationTokenFilter();
        }
    
        @Bean
        @Override
        public AuthenticationManager authenticationManagerBean() throws Exception {
            return super.authenticationManagerBean();
        }
    }
    

    4.相关联的需要定义的类包括JwtAuthenticationTokenFilter,RestAuthenticationEntryPoint,RestfulAccessDeniedHandler,AdminUserDetails,UmsAdminService
    相关具体实现

    1. JwtAuthenticationTokenFilter

    在用户名和密码校验前添加的过滤器,如果请求中有jwt的token且有效,会取出token中的用户名,然后调用SpringSecurity的API进行登录操作。

    package com.mall.mallmybatis.componet;
    
    
    import com.mall.mallmybatis.common.util.JwtTokenUtil;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    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.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;
    
    /**
     * JWT登录授权过滤器
     * Created by macro on 2018/4/26.
     */
    public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {
        private static final Logger LOGGER = LoggerFactory.getLogger(JwtAuthenticationTokenFilter.class);
        @Autowired
        private UserDetailsService userDetailsService;
        @Autowired
        private JwtTokenUtil jwtTokenUtil;
        @Value("${jwt.tokenHeader}")
        private String tokenHeader;
        @Value("${jwt.tokenHead}")
        private String tokenHead;
    
        @Override
        protected void doFilterInternal(HttpServletRequest request,
                                        HttpServletResponse response,
                                        FilterChain chain) throws ServletException, IOException {
            String authHeader = request.getHeader(this.tokenHeader);
            if (authHeader != null && authHeader.startsWith(this.tokenHead)) {
                String authToken = authHeader.substring(this.tokenHead.length());// The part after "Bearer "
                String username = jwtTokenUtil.getUserNameFromToken(authToken);
                LOGGER.info("checking username:{}", username);
                if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
                    UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
                    if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        LOGGER.info("authenticated user:{}", username);
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                }
            }
            chain.doFilter(request, response);
        }
    }
    
    1. RestAuthenticationEntryPoint
    package com.mall.mallmybatis.componet;
    
    import cn.hutool.json.JSONUtil;
    import com.mall.mallmybatis.common.api.CommonResult;
    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;
    
    /**
     * 当未登录或者token失效访问接口时,自定义的返回结果
     * Created by macro on 2018/5/14.
     */
    @Component
    public class RestAuthenticationEntryPoint implements AuthenticationEntryPoint {
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
            response.setCharacterEncoding("UTF-8");
            response.setContentType("application/json");
            response.getWriter().println(JSONUtil.parse(CommonResult.unauthorized(authException.getMessage())));
            response.getWriter().flush();
        }
    }
    
    1. RestfulAccessDeniedHandler
    package com.mall.mallmybatis.componet;
    
    import cn.hutool.json.JSONUtil;
    import com.mall.mallmybatis.common.api.CommonResult;
    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(JSONUtil.parse(CommonResult.forbidden(e.getMessage())));
            response.getWriter().flush();
        }
    }
    

    4 AdminUserDetails

    package com.mall.mallmybatis.dto;
    
    import com.mall.mallmybatis.mbg.model.UmsAdmin;
    import com.mall.mallmybatis.mbg.model.UmsPermission;
    import org.springframework.security.core.GrantedAuthority;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UserDetails;
    
    import java.util.Collection;
    import java.util.List;
    import java.util.stream.Collectors;
    
    /**
     * 用户细节表
     * @author wangxing
     * @version 2020/6/22 16:59 Administrator
     */
    public class AdminUserDetails implements UserDetails {
        private UmsAdmin umsAdmin;
        private List<UmsPermission> permissionList;
        public AdminUserDetails(UmsAdmin umsAdmin, List<UmsPermission> permissionList) {
            this.umsAdmin = umsAdmin;
            this.permissionList = permissionList;
        }
    
        @Override
        public Collection<? extends GrantedAuthority> getAuthorities() {
            //返回当前用户的权限
            return permissionList.stream()
                    .filter(permission -> permission.getValue()!=null)
                    .map(permission ->new SimpleGrantedAuthority(permission.getValue()))
                    .collect(Collectors.toList());
        }
    
        @Override
        public String getPassword() {
            return umsAdmin.getPassword();
        }
    
        @Override
        public String getUsername() {
            return umsAdmin.getUsername();
        }
    
        @Override
        public boolean isAccountNonExpired() {
            return true;
        }
    
        @Override
        public boolean isAccountNonLocked() {
            return true;
        }
    
        @Override
        public boolean isCredentialsNonExpired() {
            return true;
        }
    
        @Override
        public boolean isEnabled() {
            return umsAdmin.getStatus().equals(1);
        }
    }
    

    添加功能实现

    1. 添加UmsAdminService接口 定义权限相关功能
    package com.mall.mallmybatis.service;
    import com.mall.mallmybatis.mbg.model.UmsAdmin;
    import com.mall.mallmybatis.mbg.model.UmsPermission;
    
    import java.util.List;
    
    
    /**
     * 后台管理员Service
     * @author wangxing
     * @version 2020/6/22 15:51 Administrator
     */
    public interface UmsAdminService {
        /**
         * 根据用户名获取后台管理员
         */
        UmsAdmin getAdminByUsername(String username);
    
        /**
         * 注册功能
         */
        UmsAdmin register(UmsAdmin umsAdminParam);
    
        /**
         * 登录功能
         * @param username 用户名
         * @param password 密码
         * @return 生成的JWT的token
         */
        String login(String username, String password);
    
        /**
         * 获取用户所有权限(包括角色权限和+-权限)
         */
        List<UmsPermission> getPermissionList(Long adminId);
    }
    
    1. 实现UmsAdminService接口的实现类UmsAdminServiceImpl
    package com.mall.mallmybatis.service.impl;
    
    import com.mall.mallmybatis.common.util.JwtTokenUtil;
    import com.mall.mallmybatis.dao.UmsAdminRoleRelationDao;
    import com.mall.mallmybatis.mbg.mapper.UmsAdminMapper;
    import com.mall.mallmybatis.mbg.model.UmsAdmin;
    import com.mall.mallmybatis.mbg.model.UmsAdminExample;
    import com.mall.mallmybatis.mbg.model.UmsPermission;
    import com.mall.mallmybatis.service.UmsAdminService;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.BeanUtils;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.security.authentication.BadCredentialsException;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.AuthenticationException;
    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.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Service;
    
    import java.util.Date;
    import java.util.List;
    
    /**
     * @author wangxing
     * @version 2020/6/22 16:34 Administrator
     */
    @Service
    public class UmsAdminServiceImpl implements UmsAdminService {
        private static final Logger LOGGER = LoggerFactory.getLogger(UmsAdminServiceImpl.class);
        @Autowired
        private UserDetailsService userDetailsService;
        @Autowired
        private JwtTokenUtil jwtTokenUtil;
        @Autowired
        private PasswordEncoder passwordEncoder;
        @Value("${jwt.tokenHead}")
        private String tokenHead;
        @Autowired
        private UmsAdminMapper adminMapper;
        @Autowired
        private UmsAdminRoleRelationDao adminRoleRelationDao;
    
        /**
         * 根据用户名获取后台管理员
         * @param username
         * @return
         */
        @Override
        public UmsAdmin getAdminByUsername(String username) {
            UmsAdminExample example = new UmsAdminExample();
            example.createCriteria().andUsernameEqualTo(username);
            List<UmsAdmin> adminList = adminMapper.selectByExample(example);
            if (adminList != null && adminList.size() > 0) {
                return adminList.get(0);
            }
            return null;
        }
    
        /**
         * 注册功能
         * @param umsAdminParam
         * @return
         */
        @Override
        public UmsAdmin register(UmsAdmin umsAdminParam) {
            UmsAdmin umsAdmin = new UmsAdmin();
            BeanUtils.copyProperties(umsAdminParam, umsAdmin);
            umsAdmin.setCreateTime(new Date());
            umsAdmin.setStatus(1);
            //查询是否有相同用户名的用户
            UmsAdminExample example = new UmsAdminExample();
            example.createCriteria().andUsernameEqualTo(umsAdmin.getUsername());
            List<UmsAdmin> umsAdminList = adminMapper.selectByExample(example);
            if (umsAdminList.size() > 0) {
                return null;
            }
            //将密码进行加密操作
            String encodePassword = passwordEncoder.encode(umsAdmin.getPassword());
            umsAdmin.setPassword(encodePassword);
            adminMapper.insert(umsAdmin);
            return umsAdmin;
        }
    
        /**
         * 登录功能
         * @param username 用户名
         * @param password 密码
         * @return
         */
        @Override
        public String login(String username, String password) {
            String token = null;
            try {
                UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                if (!passwordEncoder.matches(password, userDetails.getPassword())) {
                    throw new BadCredentialsException("密码不正确");
                }
                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
                SecurityContextHolder.getContext().setAuthentication(authentication);
                token = jwtTokenUtil.generateToken(userDetails);
            } catch (AuthenticationException e) {
                LOGGER.warn("登录异常:{}", e.getMessage());
            }
            return token;
        }
    
        /**
         * 获取用户所有权限(包括角色权限和+-权限)
         * @param adminId
         * @return
         */
        @Override
        public List<UmsPermission> getPermissionList(Long adminId) {
            return adminRoleRelationDao.getPermissionList(adminId);
        }
    }
    

    3.调整Swagger的配置

    通过修改配置实现调用接口自带Authorization头,这样就可以访问需要登录的接口了。

    package com.mall.mallmybatis.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import springfox.documentation.builders.ApiInfoBuilder;
    import springfox.documentation.builders.PathSelectors;
    import springfox.documentation.builders.RequestHandlerSelectors;
    import springfox.documentation.service.ApiInfo;
    import springfox.documentation.service.ApiKey;
    import springfox.documentation.service.AuthorizationScope;
    import springfox.documentation.service.SecurityReference;
    import springfox.documentation.spi.DocumentationType;
    import springfox.documentation.spi.service.contexts.SecurityContext;
    import springfox.documentation.spring.web.plugins.Docket;
    import springfox.documentation.swagger2.annotations.EnableSwagger2;
    
    import java.util.ArrayList;
    import java.util.List;
    
    /**
     * 在线API配置类
     * @author wangxing
     * @version 2020/6/22 11:43 Administrator
     */
    @Configuration
    @EnableSwagger2
    public class Swagger2Config {
        @Bean
        public Docket createRestApi(){
            return new Docket(DocumentationType.SWAGGER_2)
                    .apiInfo(apiInfo())
                    .select()
                    //为指定包路径下的类生成API文档
                    .apis(RequestHandlerSelectors.basePackage("com.mall.mallmybatis.controller"))
                    //为有@Api注解的Controller生成API文档
    //                .apis(RequestHandlerSelectors.withClassAnnotation(Api.class))
                    //为有@ApiOperation注解的方法生成API文档
    //                .apis(RequestHandlerSelectors.withMethodAnnotation(ApiOperation.class))
                    .paths(PathSelectors.any())
                    .build()
                    //添加登录认证
    //        设置请求头信息
                    .securitySchemes(securitySchemes())
    //        设置需要登录认证的路径
                    .securityContexts(securityContexts());
        }
        private ApiInfo apiInfo() {
            return new ApiInfoBuilder()
                    .title("SwaggerUI")
                    .description("mall learning")
                    .contact("macro")
                    .version("1.0")
                    .build();
        }
        private List<ApiKey> securitySchemes() {
            //设置请求头信息
            List<ApiKey> result = new ArrayList<>();
            ApiKey apiKey = new ApiKey("Authorization", "Authorization", "header");
            result.add(apiKey);
            return result;
        }
    
        private List<SecurityContext> securityContexts() {
            //设置需要登录认证的路径
            List<SecurityContext> result = new ArrayList<>();
            result.add(getContextByPath("/brand/.*"));
            return result;
        }
    
        private SecurityContext getContextByPath(String pathRegex){
            return SecurityContext.builder()
                    .securityReferences(defaultAuth())
                    .forPaths(PathSelectors.regex(pathRegex))
                    .build();
        }
    
        private List<SecurityReference> defaultAuth() {
            List<SecurityReference> result = new ArrayList<>();
            AuthorizationScope authorizationScope = new AuthorizationScope("global", "accessEverything");
            AuthorizationScope[] authorizationScopes = new AuthorizationScope[1];
            authorizationScopes[0] = authorizationScope;
            result.add(new SecurityReference("Authorization", authorizationScopes));
            return result;
        }
    }
    
    

    4.给PmsBrandController接口中方法授权

    • 给查询接口添加pms:brand:read权限
    • 给修改接口添加pms:brand:update权限
    • 给删除接口添加pms:brand:delete权限
    • 给添加接口添加pms:brand:create权限
      修改后PmsBrandController
    package com.mall.mallmybatis.controller;
    
    import com.mall.mallmybatis.common.api.CommonPage;
    import com.mall.mallmybatis.common.api.CommonResult;
    import com.mall.mallmybatis.mbg.model.PmsBrand;
    import com.mall.mallmybatis.service.PmsBrandService;
    import io.swagger.annotations.Api;
    import io.swagger.annotations.ApiOperation;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.security.access.prepost.PreAuthorize;
    import org.springframework.stereotype.Controller;
    import org.springframework.validation.BindingResult;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.List;
    
    /**
     * @author wangxing
     * @version 2020/6/22 6:10 Administrator
     */
    @Api(tags = {"PmsBrandController","商品品牌管理"})
    @Controller
    @RequestMapping("/brand")
    public class PmsBrandController {
        @Autowired
        private PmsBrandService pmsBrandService;
    
        private  static final Logger LOGGER = LoggerFactory.getLogger(PmsBrandController.class);
        @ApiOperation("获取所有品牌列表")
        @RequestMapping(value = "listAll",method = RequestMethod.GET)
        @ResponseBody
        @PreAuthorize("hasAuthority('pms:brand:read')")
        public CommonResult<List<PmsBrand>> getBrandList() {
            return CommonResult.success(pmsBrandService.listAllBrand());
        }
    
        @ApiOperation("添加品牌")
        @RequestMapping(value = "/create", method = RequestMethod.POST)
        @ResponseBody
        @PreAuthorize("hasAuthority('pms:brand:create')")
        public CommonResult createBrand(@RequestBody PmsBrand pmsBrand) {
            CommonResult commonResult;
            int count = pmsBrandService.createBrand(pmsBrand);
            if (count == 1) {
                commonResult = CommonResult.success(pmsBrand);
                LOGGER.debug("createBrand success:{}", pmsBrand);
            } else {
                commonResult = CommonResult.failed("操作失败");
                LOGGER.debug("createBrand failed:{}", pmsBrand);
            }
            return commonResult;
        }
        @ApiOperation("更新指定id品牌信息")
        @RequestMapping(value = "/update/{id}", method = RequestMethod.POST)
        @ResponseBody
        @PreAuthorize("hasAuthority('pms:brand:update')")
        public CommonResult updateBrand(@PathVariable("id") Long id, @RequestBody PmsBrand pmsBrandDto, BindingResult result) {
            CommonResult commonResult;
            int count = pmsBrandService.updateBrand(id, pmsBrandDto);
            if (count == 1) {
                commonResult = CommonResult.success(pmsBrandDto);
                LOGGER.debug("updateBrand success:{}", pmsBrandDto);
            } else {
                commonResult = CommonResult.failed("操作失败");
                LOGGER.debug("updateBrand failed:{}", pmsBrandDto);
            }
            return commonResult;
        }
        @ApiOperation("删除指定id的品牌")
        @RequestMapping(value = "/delete/{id}", method = RequestMethod.GET)
        @ResponseBody
        @PreAuthorize("hasAuthority('pms:brand:delete')")
        public CommonResult deleteBrand(@PathVariable("id") Long id) {
            int count = pmsBrandService.deleteBrand(id);
            if (count == 1) {
                LOGGER.debug("deleteBrand success :id={}", id);
                return CommonResult.success(null);
            } else {
                LOGGER.debug("deleteBrand failed :id={}", id);
                return CommonResult.failed("操作失败");
            }
        }
        @ApiOperation("分页查询品牌列表")
        @RequestMapping(value = "/list", method = RequestMethod.GET)
        @ResponseBody
        @PreAuthorize("hasAuthority('pms:brand:read')")
        public CommonResult<CommonPage<PmsBrand>> listBrand(@RequestParam(value = "pageNum", defaultValue = "1") Integer pageNum,
                                                            @RequestParam(value = "pageSize", defaultValue = "3") Integer pageSize) {
            List<PmsBrand> brandList = pmsBrandService.listBrand(pageNum, pageSize);
            return CommonResult.success(CommonPage.restPage(brandList));
        }
        @ApiOperation("获取指定id的品牌详情")
        @RequestMapping(value = "/{id}", method = RequestMethod.GET)
        @ResponseBody
        @PreAuthorize("hasAuthority('pms:brand:read')")
        public CommonResult<PmsBrand> brand(@PathVariable("id") Long id) {
            return CommonResult.success(pmsBrandService.getBrand(id));
        }
    }
    

    测试相关实现

    1. 启动项目
    2. 访问SwaggerUI
      http://localhost:8080/swagger-ui.html
      3.未登录状态下访问连接
      image.png
      image.png

    4.执行登录方法获取token


    image.png
    image.png

    5.将获取的token放入到Authorize中


    image.png

    相关文章

      网友评论

        本文标题:电商项目mall学习(4)整合SpringSecurity和JW

        本文链接:https://www.haomeiwen.com/subject/myzffktx.html