springboot中使用spring-security进行登陆控制 - Go语言中文社区

springboot中使用spring-security进行登陆控制


在springboot项目中使用spring-security实现登陆验证,登陆成功则返回角色及权限信息,登陆失败则返回失败原因。
在实现此功能的时候,在网上找了大量教程,但是均没有符合我需求的,最后在参考一篇文档的基础上进行了代码实现。

结果展示

使用restClient发送post请求,post体中携带了username和password等信息。后台会根据此账号查询数据库判断此用户是否存在,如果存在则进一步进行账号密码的验证,验证通过再去查询此账号拥有的角色及权限信息。

在这里插入图片描述

具体实现代码如下:

1. 引入依赖

<dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>${mybatis.plus.version}</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>${druild.version}</version>
        </dependency>
        
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
            <version>${spring.boot.version}</version>
        </dependency>
        
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>

        <dependency>
            <groupId>org.json</groupId>
            <artifactId>json</artifactId>
            <version>20160810</version>
        </dependency>

        <dependency>
            <groupId>com.jayway.jsonpath</groupId>
            <artifactId>json-path</artifactId>
            <version>2.4.0</version>
        </dependency>

在上面除了引入了spring-security的依赖之外,还有三种json解析的依赖,都有不同的作用,还有一些其他依赖是连接数据库所用。

2. 配置User类

package com.mas.leadsscoring.process.vo;

import com.mas.leadsscoring.process.entity.TtRole;
import lombok.Data;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;

/**
 * @Description spring-security的user对象
 * @Author LiuYue
 * @Date 2019/3/6
 * @Version 1.0
 */
@Data
public class UserVO implements UserDetails {

    /**
     * 用户ID
     */
    private String userId;

    /**
     * 用户账号
     */
    private String user;

    /**
     * 用户名
     */
    private String userName;

    /**
     * 密码
     */
    private String password;

    /**
     * 是否为回收站,0表示不是,1表示是
     */
    private String isRecycle;

    /**
     * 是否可用。Y表示可用,N表示不可用
     */
    private String isEnable;

    /**
     * 创建人
     */
    private String createBy;

    /**
     * 创建时间
     */
    private Date createTime;

    /**
     * 更新人
     */
    private String updateBy;

    /**
     * 更新时间
     */
    private Date updateTime;

    /**
     * 备注
     */
    private String comment;

    /**
     * 用户角色
     **/
    private List<TtRole> roles;


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<GrantedAuthority> authorities = new ArrayList<>();
        for (TtRole role : roles) {
            authorities.add(new SimpleGrantedAuthority("ROLE_"+role.getRole()));
        }
        return authorities;
    }

    @Override
    public String getUsername() {
        return userName;
    }

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

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

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

    @Override
    public boolean isEnabled() {

        return "y".equals(isEnable)  ;
    }

}

User类实现了UserDetails接口,并定义了role属性,此属性是一个对象,对象中存放了角色的一些属性。并重写了getAuthorities()方法,在此方法中将角色信息加入到List中。

3. 配置TtUserServiceImpl方法

package com.mas.leadsscoring.process.service.impl;

import com.baomidou.mybatisplus.core.conditions.Wrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.mas.leadsscoring.common.util.TransformUtils;
import com.mas.leadsscoring.process.entity.TtRole;
import com.mas.leadsscoring.process.entity.TtUser;
import com.mas.leadsscoring.process.mapper.TtRoleMapper;
import com.mas.leadsscoring.process.mapper.TtUserMapper;
import com.mas.leadsscoring.process.service.ITtUserService;
import com.mas.leadsscoring.common.base.service.impl.BaseServiceImpl;
import com.mas.leadsscoring.process.vo.UserVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

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

/**
 * <p>
 * 'leads_process.tt_table_config' is not BASE TABLE 服务实现类
 * </p>
 *
 * @author suntianchi
 * @since 2019-03-05
 */
@Service("ttUserServiceImpl")
public class TtUserServiceImpl extends BaseServiceImpl<TtUserMapper, TtUser> implements ITtUserService {

    private TtUserMapper ttUserMapper;
    private TtRoleMapper ttRoleMapper;

    @Autowired
    public TtUserServiceImpl(TtUserMapper ttUserMapper, TtRoleMapper ttRoleMapper) {
        this.ttUserMapper = ttUserMapper;
        this.ttRoleMapper = ttRoleMapper;
    }

    @Override
    public UserDetails loadUserByUsername(String s) throws UsernameNotFoundException {

        TtUser ttUser = ttUserMapper.getUserByUsername(s);

        if (ttUser == null) {
            throw new UsernameNotFoundException("用户名不对");
        }

        List<TtRole> roles = ttRoleMapper.getUserRole(ttUser.getUserId());

        //由于mybatis-plus会将TtUser对象中的属性封装到sql中进行查询,所以没有直接在TtUser对象中增加Role属性,而是通过TransformUtils中的beanToBean方法,将TtUser转换成实现UserDetail方法的UserVO对象。beanToBean方法会将相同的属性进行值传递,不同属性则不会变化。
        UserVO userVO = TransformUtils.beanToBean(ttUser, UserVO.class);
        userVO.setRoles(roles);
        return userVO;
    }
}

TtUserServiceImpl方法继承了BaseServiceImpl方法,此方法是mybatisPlus提供的方法,里面封装了一些基本的增删改查的方法。

TtUserServiceImpl还实现了ITtUserService接口,此接口继承了UserDetailsService接口,所以最后也就相当于TtUserServiceImpl实现了UserDetailsService接口。

public interface ITtUserService extends IBaseService<TtUser>, UserDetailsService {

    @Override
    UserDetails loadUserByUsername(String s) throws UsernameNotFoundException;
}

UserDetailsService接口是spring-security框架中提供的一个接口,实现此接口的loadUserByUsername方法,最后并返回查询到的用户信息,spring-security框架就会对此用户进行账号密码校验,这些会在待会的debug模式中一步一步展示。

3. 最重要的WebSecurityConfig

package com.mas.leadsscoring.process.config.security;

import com.mas.leadsscoring.process.entity.RespBean;
import com.mas.leadsscoring.process.service.impl.TtUserServiceImpl;
import com.mas.leadsscoring.process.util.CommonUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.security.access.AccessDeniedException;
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.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

/**
 * @Description 验证授权配置类
 * @Author LiuYue
 * @Date 2019/3/5
 * @Version 1.0
 */
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true) //开启security注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private CustomLoginHandler customLoginHandler;

    @Autowired
    private CustomLogoutHandler customLogoutHandler;

    @Autowired
    private CustomAccessDeniedHandler customAccessDeniedHandler;

    @Autowired
    private TtUserServiceImpl ttUserService;

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

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

        http.authorizeRequests().antMatchers(HttpMethod.GET).permitAll()
                .antMatchers(HttpMethod.POST).permitAll()
                .antMatchers("/api/**").permitAll();

        http.formLogin();

        http.logout().logoutUrl("/api/session/logout")
                // 登出前调用,可用于日志
                .addLogoutHandler(customLogoutHandler)
                // 登出后调用,用户信息已不存在
                .logoutSuccessHandler(customLogoutHandler);

        http.exceptionHandling()
                // 已登入用户的权限错误
                .accessDeniedHandler(customAccessDeniedHandler)
                // 未登入用户的权限错误
                .authenticationEntryPoint(customAccessDeniedHandler);

        http.csrf().disable();

        http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

    }

    private static void responseText(HttpServletResponse response, String content) throws IOException {
        response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
        byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
        response.setContentLength(bytes.length);
        response.getOutputStream().write(bytes);
        response.flushBuffer();
    }

    private CustomAuthenticationFilter customAuthenticationFilter() throws Exception {
        CustomAuthenticationFilter filter = new CustomAuthenticationFilter();
        filter.setAuthenticationSuccessHandler(customLoginHandler);
        filter.setAuthenticationFailureHandler(customLoginHandler);
        filter.setAuthenticationManager(authenticationManager());
        filter.setFilterProcessesUrl("/api/login");
        return filter;
    }

    @Component
    public static class CustomLoginHandler extends RespBean implements AuthenticationSuccessHandler, AuthenticationFailureHandler {
        // Login Success
        @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                            Authentication authentication) throws IOException {
            responseText(response, objectResult(CommonUtils.getJSON(authentication)));
        }

        // Login Failure
        @Override
        public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                            AuthenticationException exception) throws IOException {
            responseText(response, errorMessage(exception.getMessage()));
        }
    }

    @Component
    public static class CustomAccessDeniedHandler extends RespBean implements AuthenticationEntryPoint, AccessDeniedHandler {
        // NoLogged Access Denied
        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response,
                             AuthenticationException authException) throws IOException {
            responseText(response, errorMessage(authException.getMessage()));
        }

        // Logged Access Denied
        @Override
        public void handle(HttpServletRequest request, HttpServletResponse response,
                           AccessDeniedException accessDeniedException) throws IOException {
            responseText(response, errorMessage(accessDeniedException.getMessage()));
        }
    }

    @Component
    public static class CustomLogoutHandler extends RespBean implements LogoutHandler, LogoutSuccessHandler {
        // Before Logout
        @Override
        public void logout(HttpServletRequest request, HttpServletResponse response, Authentication authentication) {

        }

        // After Logout
        @Override
        public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response,
                                    Authentication authentication) throws IOException {
            responseText(response, objectResult(CommonUtils.getJSON(null)));
        }
    }
}
  • configure(AuthenticationManagerBuilder auth):在此方法中进行密码加密操作。
  • configure(HttpSecurity http): 在此方法中进行授权操作,允许接收某些特定的请求,比如我在此处就定义了get/post可以进来,此处的配置是不合理的,后面可以根据需要进行具体的设置。
  • http.addFilterAt(customAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class): 增加了一个filter,此filter的具体作用可以在内部customAuthenticationFilter方法看出,是对/api/login的请求进行过滤,当登陆成功或者失败会调用CustomLoginHandler,此对象是通过@component注解修饰的,在spring启动的时候就会进行实例化,此方法中分别定义了当成功登陆与登陆失败时候的方法。

4. CustomLoginHandler类

package com.mas.leadsscoring.process.config.security;

import com.jayway.jsonpath.DocumentContext;
import com.jayway.jsonpath.JsonPath;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;

/**
 * @Description security登录过滤
 * @Author LiuYue
 * @Date 2019/3/6
 * @Version 1.0
 */
public class CustomAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        UsernamePasswordAuthenticationToken authRequest;
        try (InputStream is = request.getInputStream()) {
            DocumentContext context = JsonPath.parse(is);
            String username = context.read("$.username", String.class);
            String password = context.read("$.password", String.class);
            authRequest = new UsernamePasswordAuthenticationToken(username, password);
        } catch (Exception e) {
            e.printStackTrace();
            authRequest = new UsernamePasswordAuthenticationToken("", "");
        }
        setDetails(request, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }
}

在此过滤器中使用jsonpath获取request请求中的username和password信息,并将其配置到UsernamePasswordAuthenticationToken对象中,后面会根据此对象的密码与数据库查询到的密码进行比对。

5. RespBean对象

package com.mas.leadsscoring.process.entity;

import org.json.JSONObject;
import org.springframework.http.MediaType;

/**
 * @Description 后台返回前端对象
 * @Author LiuYue
 * @Date 2019/3/5
 * @Version 1.0
 */
public class RespBean {

    protected static final String MEDIA_TYPE = MediaType.APPLICATION_JSON_UTF8_VALUE;

    /**
     * @Description 返回成功
     * @Author LiuYue
     * @Date 2019/3/6
     * @Param [object]
     * @return java.lang.String
     **/
    protected String objectResult(Object object) {
        JSONObject root = new JSONObject();
        root.put("success", true);
        root.put("data", object);
        return root.toString();
    }

    /**
     * @Description 返回失败
     * @Author LiuYue
     * @Date 2019/3/6
     * @Param [message]
     * @return java.lang.String
     **/
    protected String errorMessage(String message) {
        JSONObject root = new JSONObject();
        root.put("success", false);
        root.put("message", message);
        return root.toString();
    }

}

此对象就是用作登陆失败与登陆成功封装返回信息内容。

6. Debug过程

项目启动

在项目开始启动的时候spring-security会进行相应对象的初始化。此处进行继承WebSecurityConfigurerAdapter对象的初始化,可以看见进行了密码加密,以及对应请求过滤的配置。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

前端发出请求(/api/user)

根据此请求进行拦截,进入到我们配置的过滤器中:

在这里插入图片描述

根据用户名查询用户是否存在

如果用户存在,则会返回此用户的密码角色等信息。

在这里插入图片描述

判断用户密码是否匹配

由于UserVo实现了Userdetial接口,此方法中会根据Userdetial查询其对象中的信息,进行密码判断。

在这里插入图片描述

登陆成功

在这里插入图片描述

获取角色信息

如果用户密码校验通过,则会获取角色信息

在这里插入图片描述

封装返回对象

在这里插入图片描述

返回给前端

在这里插入图片描述

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/nb7474/article/details/88696205
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢