SpringSecurity

二、SpringSecurity与数据库连接

2020-05-04  本文已影响0人  紫荆秋雪_文

源码下载

由于SpringSecurity是一个安全框架,所以SpringSecurity就会有认证(用户登录是否成功)和授权(认证成功后的用户有那些权限)的功能。所以接下来连接数据库来看看是如何使用。

一、数据库表(RBAC)

二、自定义用户名和密码

上一节体验一下SpringSecurity,但是登陆的用户名和密码都是固定的,显然是不符合我们真实开发。

1、UserDetails是SpringSecurity用户信息接口


@Setter
public class DemoUserDetails implements UserDetails {

    private String username;
    private String password;
    private int enabled;
    private Collection<? extends GrantedAuthority> authorities;


    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

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

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

    public boolean isAccountNonExpired() {
        return true;
    }

    public boolean isAccountNonLocked() {
        return true;
    }

    public boolean isCredentialsNonExpired() {
        return true;
    }

    public boolean isEnabled() {
        return this.enabled == 1;
    }
}

2、UserDetailsService是SpringSecurity认证用户并且返回用户信息接口


@Component
public class DemoUserDetailsServiceImpl implements UserDetailsService {

    @Autowired
    private IDemoUserMapper userMapper;

    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // 通过用户名获取用户信息
        DemoUserDetails userInfo = this.userMapper.fetchUserInfoByUserName(username);

        if (userInfo == null) {
            throw new RuntimeException("用户不存在");
        }

        // 通过用户名获取用户角色资源
        List<String> roleCodes = this.userMapper.fetchRoleCodesByUserName(username);

        // 通过用户名获取用户权限
        List<String> authorties = this.userMapper.fetchMenuUrlsByUserName(username);

        // 角色也是一种用户权限
        if (authorties != null && roleCodes != null) {
            authorties.addAll(roleCodes);
        }

        userInfo.setAuthorities(
                AuthorityUtils.commaSeparatedStringToAuthorityList(
                        String.join(",", authorties)
                )
        );
        return userInfo;
    }

}

@Mapper
public interface IDemoUserMapper {

    /**
     * @param username 用户名称
     * @return 用户信息
     */
    @Select("SELECT * FROM t_user WHERE username = #{username}")
    DemoUserDetails fetchUserInfoByUserName(@Param("username") String username);


    /**
     * @param username 用户名称
     * @return 角色id列表
     */
    @Select("SELECT ur.role_id \n" +
            "FROM t_user_role ur \n" +
            "LEFT JOIN t_user u\n" +
            "ON ur.user_id = u.id\n" +
            "WHERE u.username = #{username}")
    List<String> fetchRoleIdsByUserName(@Param("username") String username);

    /**
     * @param username 用户名称
     * @return 角色code列表
     */
    @Select("SELECT r.role_code\n" +
            "FROM t_role r\n" +
            "LEFT JOIN (t_user_role ur \n" +
            "LEFT JOIN t_user u\n" +
            "ON ur.user_id = u.id)\n" +
            "ON r.id = ur.role_id\n" +
            "WHERE u.username = #{username}")
    List<String> fetchRoleCodesByUserName(@Param("username") String username);

    /**
     * @param username 用户名称
     * @return 权限url列表
     */
    @Select("SELECT m.url\n" +
            "FROM t_menu m\n" +
            "LEFT JOIN (t_role_menu rm\n" +
            "LEFT JOIN (t_user_role ur \n" +
            "LEFT JOIN t_user u\n" +
            "ON ur.user_id = u.id)\n" +
            "ON rm.role_id = ur.role_id)\n" +
            "ON m.id = rm.menu_id\n" +
            "WHERE u.username = #{username}")
    List<String> fetchMenuUrlsByUserName(@Param("username") String username);


    /**
     * @param roleIds 角色id列表
     * @return 权限url列表
     */
    @Select({
            "<script>",
                "SELECT m.url\n" +
                    "FROM t_menu m\n" +
                    "LEFT JOIN t_role_menu rm\n" +
                    "ON m.id = rm.menu_id\n" +
                    "WHERE rm.role_id IN ",
                "<foreach collection='roleIds' item='roleId' open='(' separator=',' close=')'>",
                    "#{roleId}",
                "</foreach>",
            "</script>"
    })
    List<String> fetchMenuUrlsByRoleIds(@Param("roleIds") List<String> roleIds);


}

三、自定义登录页

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
    <script src="https://cdn.staticfile.org/jquery/1.12.3/jquery.min.js"></script>
</head>
<body>


<form action="/authentication/form" method="post">
    <span>用户名称</span><input type="text" name="username"/> <br>
    <span>用户密码</span><input type="password" name="password"/> <br>
    <input type="submit" value="登陆">
</form>

</body>
</html>
/**
 * Web端security配置
 */
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
//        http.httpBasic() // 配置弹窗登录界面
        http.formLogin()
                .loginPage("/bw-login.html")
                .and()
                .authorizeRequests()
                .anyRequest()
                .authenticated();
    }
}
自定义登录.png
/**
 * Web端security配置
 */
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .formLogin()
                .loginPage("/bw-login.html")    // 自定义登录界面
                .loginProcessingUrl("/authentication/form") // 默认处理的/login,自定义登录界面需要指定请求路径
                .and()
                .authorizeRequests()
                .antMatchers("/bw-login.html").permitAll()
                .anyRequest()
                .authenticated();
    }
}
/**
 * Web端security配置
 */
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .formLogin()
                .loginPage("/bw-login.html")    // 自定义登录界面
                .loginProcessingUrl("/authentication/form") // 默认处理的/login,自定义登录界面需要指定请求路径
                .and()
                .authorizeRequests()
                .antMatchers("/bw-login.html").permitAll()
                .anyRequest()
                .authenticated();
    }
}
public UsernamePasswordAuthenticationFilter() {
    super(new AntPathRequestMatcher("/login", "POST"));
}
/**
 * 当需要身份认证时,跳转到这里
 */
@RestController
public class BrowserSecurityController {


    private Logger logger = LoggerFactory.getLogger(getClass());
    // 获取session缓存
    private RequestCache requestCache = new HttpSessionRequestCache();
    // 跳转工具类
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();


    /**
     * 当需要身份认证时,跳转到这里
     *
     * @param request
     * @param response
     * @return
     * @throws IOException
     */
    @RequestMapping("/authentication/require")
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
    public R requireAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws IOException {

        SavedRequest savedRequest = requestCache.getRequest(request, response);

        if (savedRequest != null) {
            String targetUrl = savedRequest.getRedirectUrl();
            String loginPage = "/bw-login.html";
            logger.info("引发跳转的请求是:" + targetUrl);
            logger.info("引发跳转的请求是:" + loginPage);
            if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
                redirectStrategy.sendRedirect(request, response, loginPage);
            }
        }
        return new R("访问的服务需要身份认证,请引导用户到登录页");
    }
}
/**
 * Web端security配置
 */
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
                .formLogin()
                .loginPage("/authentication/require")    // 当需要身份认证时,跳转到这里
                .loginProcessingUrl("/authentication/form") // 默认处理的/login,自定义登录界面需要指定请求路径
                .and()
                .authorizeRequests()
                .antMatchers(
                        "/bw-login.html",
                        "/authentication/require").permitAll()
                .anyRequest()
                .authenticated();
    }
}
/**
 * 系统配置类
 */
@Getter
@ConfigurationProperties("raven.security")
public class RavenSecurityProperties {
    // 浏览器相关配置
    private BrowserProperties browser = new BrowserProperties();
}
/**
 * 浏览器相关配置类
 */
@Data
public class BrowserProperties {
    private String loginPage = "/bw-login.html";
}
/**
 * 当需要身份认证时,跳转到这里
 */
@RestController
public class BrowserSecurityController {


    private Logger logger = LoggerFactory.getLogger(getClass());
    // 获取session缓存
    private RequestCache requestCache = new HttpSessionRequestCache();
    // 跳转工具类
    private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();

    @Autowired
    private RavenSecurityProperties securityProperties;

    /**
     * 当需要身份认证时,跳转到这里
     *
     * @param request
     * @param response
     * @return
     * @throws IOException
     */
    @RequestMapping("/authentication/require")
    @ResponseStatus(code = HttpStatus.UNAUTHORIZED)
    public R requireAuthentication(HttpServletRequest request, HttpServletResponse response)
            throws IOException {

        SavedRequest savedRequest = requestCache.getRequest(request, response);

        if (savedRequest != null) {
            String targetUrl = savedRequest.getRedirectUrl();
            String loginPage = this.securityProperties.getBrowser().getLoginPage();
            logger.info("引发跳转的请求是:" + targetUrl);
            logger.info("引发跳转的请求是:" + loginPage);
            if (StringUtils.endsWithIgnoreCase(targetUrl, ".html")) {
                redirectStrategy.sendRedirect(request, response, loginPage);
            }
        }
        return new R("访问的服务需要身份认证,请引导用户到登录页");
    }
}
/**
 * Web端security配置
 */
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private RavenSecurityProperties securityProperties;


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 配置登录界面
        String loginPage = this.securityProperties.getBrowser().getLoginPage();

        http.csrf().disable()
                .formLogin()
                .loginPage("/authentication/require")    // 当需要身份认证时,跳转到这里
                .loginProcessingUrl("/authentication/form") // 默认处理的/login,自定义登录界面需要指定请求路径
                .and()
                .authorizeRequests()
                .antMatchers("/authentication/require",
                        loginPage
                ).permitAll()
                .anyRequest()
                .authenticated();
    }
}
raven:
  security:
    browser:
      loginPage: /demo-login.html

四、自定义成功处理器

目前请求资源登录成功后会跳转到默认的界面或者是请求的资源页面上,为了更加灵活,需要自定义成功处理器来判断是直接返回成功页面还是返回JSON数据让用户自己决定如何处理

@Component
public class BrowserAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    private ObjectMapper objectMapper;
    private Logger logger = LoggerFactory.getLogger(getClass());

    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        logger.info("登录成功");
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(this.objectMapper.writeValueAsString(authentication));
    }

}
/**
 * Web端security配置
 */
@Configuration
public class BrowserSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private RavenSecurityProperties securityProperties;
    @Autowired
    private BrowserAuthenticationSuccessHandler browserAuthenticationSuccessHandler;


    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 配置登录界面
        String loginPage = this.securityProperties.getBrowser().getLoginPage();

        http.csrf().disable()
                .formLogin()
                .loginPage("/authentication/require")    // 当需要身份认证时,跳转到这里
                .loginProcessingUrl("/authentication/form") // 默认处理的/login,自定义登录界面需要指定请求路径
                .successHandler(this.browserAuthenticationSuccessHandler)
                .and()
                .authorizeRequests()
                .antMatchers("/authentication/require",
                        loginPage
                ).permitAll()
                .anyRequest()
                .authenticated();
    }
}
成功处理器返回JSON格式数据.png
/**
 * 浏览器相关配置类
 */
@Data
public class BrowserProperties {
    private String loginPage = "/bw-login.html";
    private RavenLoginType loginType = RavenLoginType.JSON;
}
@Component
public class BrowserAuthenticationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private RavenSecurityProperties securityProperties;

    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        logger.info("登录成功处理器");
        if (RavenLoginType.JSON.equals(this.securityProperties.getBrowser().getLoginType())) {
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(this.objectMapper.writeValueAsString(authentication));
        }
        else {
            super.onAuthenticationSuccess(request, response, authentication);
        }
    }
}
raven:
  security:
    browser:
      loginPage: /demo-login.html
      loginType: REDIRECT

这样便可以配置返回JSON格式数据还是直接跳转,同理错误处理器也一样,代码如下

@Component
public class BrowserAuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler {

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Autowired
    private ObjectMapper objectMapper;
    @Autowired
    private RavenSecurityProperties securityProperties;


    public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException {

        logger.info("自定义失败处理器");

        if (RavenLoginType.JSON.equals(this.securityProperties.getBrowser().getLoginType())) {

            response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
            response.setContentType("application/json;charset=UTF-8");
            response.getWriter().write(this.objectMapper.writeValueAsString(exception));
        }
        else {
            super.onAuthenticationFailure(request, response, exception);
        }
    }

}
上一篇 下一篇

猜你喜欢

热点阅读