Spring Boot整合Spring Security Spring Boot整合Spring Security

Spring Boot整合Spring Security

原创  2017年02月19日 13:48:03
  • 25446

更多内容请访问http://lxgandlz.cn

本文讲述Spring Boot整合Spring Security在方法上使用注解实现权限控制,使用自定义UserDetailService,从MySQL中加载用户信息。使用Security自带的MD5加密,对用户密码进行加密。页面模板采用thymeleaf引擎。 
源码地址:https://github.com/li5454yong/springboot-security.git

1、引入pom依赖

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.4.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security.oauth</groupId>
            <artifactId>spring-security-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.34</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.15</version>
        </dependency>
    </dependencies>
这里使用 druid连接池,Spring Data Jpa实现数据库访问。

2、配置Spring Security

@Configuration
@EnableWebMvcSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)//开启security注解
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }


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

        //允许所有用户访问"/"和"/home"
        http.authorizeRequests()
                .antMatchers("/", "/home").permitAll()
                //其他地址的访问均需验证权限
                .anyRequest().authenticated()
                .and()
                .formLogin()
                //指定登录页是"/login"
                .loginPage("/login")
                .defaultSuccessUrl("/hello")//登录成功后默认跳转到"/hello"
                .permitAll()
                .and()
                .logout()
                .logoutSuccessUrl("/home")//退出登录后的默认url是"/home"
                .permitAll();

    }



    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

        auth
            .userDetailsService(customUserDetailsService())
            .passwordEncoder(passwordEncoder());

    }

    /**
     * 设置用户密码的加密方式为MD5加密
     * @return
     */
    @Bean
    public Md5PasswordEncoder passwordEncoder() {
        return new Md5PasswordEncoder();

    }

    /**
     * 自定义UserDetailsService,从数据库中读取用户信息
     * @return
     */
    @Bean
    public CustomUserDetailsService customUserDetailsService(){
        return new CustomUserDetailsService();
    }

}
这里只做了基本的配置,设置了登录url、登录成功后跳转的url、退出后跳转到的url。 使用 @EnableGlobalMethodSecurity(prePostEnabled = true)这个注解,可以开启security的注解,我们可以在需要控制权限的方法上面使用@PreAuthorize,@PreFilter这些注解。

3、自定义userDetailService

public class CustomUserDetailsService implements UserDetailsService {
    @Autowired  //数据库服务类
    private SUserService suserService;

    @Override
    public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
        //SUser对应数据库中的用户表,是最终存储用户和密码的表,可自定义
        //本例使用SUser中的email作为用户名:
        SUser user = suserService.findUserByEmail(userName); 

        if (user == null) {

            throw new UsernameNotFoundException("UserName " + userName + " not found");

        }

        // SecurityUser实现UserDetails并将SUser的Email映射为username
        SecurityUser securityUser = new SecurityUser(user);
        Collection<SimpleGrantedAuthority> authorities = new ArrayList<SimpleGrantedAuthority>();
        authorities.add(new SimpleGrantedAuthority("ROLE_ADMIN"));
        return securityUser; 

    }

}
这里只需要实现UserDetailsService 接口,重写loadUserByUsername方法,从数据库中取出用户信息。最后返回一个UserDetails 实现类。

4、定义错误处理配置

@Configuration
public class ErrorPageConfig {

    @Bean
    public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer(){
        return new MyCustomizer();
    }

    private static class MyCustomizer implements EmbeddedServletContainerCustomizer {

        @Override
        public void customize(ConfigurableEmbeddedServletContainer container) {
            container.addErrorPages(new ErrorPage(HttpStatus.FORBIDDEN, "/403"));
        }

    }

}
访问发生错误时,跳转到”/403”.

5、Controller接口

@Controller
public class IndexController {

    @Resource
    private SUserService sUserService;

    @RequestMapping("/home")
    public String home() {
        return "home";

    }


    @PreAuthorize("hasRole('user')")
    @RequestMapping(value = "/admin",method = RequestMethod.GET)
    public String toAdmin(){

        return "helloAdmin";
    }

    @RequestMapping("/hello")
    public String hello() {

        return "hello";

    }

    @RequestMapping("/login")
    public String login(){
        return "login";
    }

    @RequestMapping("/")
    public String root() {
        return "index";

    }

    @RequestMapping("/403")
    public String error(){
        return "403";
    }
}

在toAdmin()方法上面使用了@PreAuthorize(“hasRole(‘user’)”),表示访问这个方法需要拥有user角色。如果希望控制到权限层面,可以使用@PreAuthorize(“hasPermission()”)。这里只是用了其中的一个用法,更多的使用方法可以去看官方文档。需要注意的是,Spring Security默认的角色前缀是”ROLE_”,使用hasRole方法时已经默认加上了,因此我们在数据库里面的用户角色应该是“ROLE_user”,在user前面加上”ROLE_”前缀。

6、测试

启动项目,访问http://localhost:1130/login 

点击登录后进入到“/hello”

点击跳转到管理员页面

在后台的“/admin”这个url对应的方法上面,限制了用户必须要拥有“user”角色。在数据库中也设置了登录的用户有这个角色。 
现在我们修改数据库中的用户角色,改为“ROLE_admin”。退出登录后重新登录,再次点击“前往管理员页面”按钮,会跳转到如下页面。 

因为现在没有了“user”权限,所以访问的时候抛出了异常,被拦截后重定向到了“/403”。

7、POST方式访问,错误码403

首先,把“/admin”改为POST请求

    @PreAuthorize("hasRole('user')")
    @RequestMapping(value = "/admin",method = RequestMethod.POST)
    public String toAdmin(){
        return "helloAdmin";
    }

把“前往管理员页面”按钮的请求方式从原来的form表达get提交,改为ajax方式POST提交。至于为什么不是用form的POST提交后面再讲。先修改代码

扫描二维码关注公众号,回复: 58711 查看本文章
<body>

<h1 th:inline="text">Hello [[${#httpServletRequest.remoteUser}]]!</h1>

<!--<form th:action="@{/logout}" method="post">

    <input type="submit" value="Sign Out"/>
</form>
<form th:action="@{/admin}" method="get">
    <input th:type="submit" th:value="前往管理员页面"/>

</form>-->
<a th:href="@{/admin}">前往管理员用户页面</a>
<input th:type="submit" onclick="testPost()" th:value="前往管理员页面"/>
</body>
<script>
    function testPost() {
        $.ajax({
            url:"/admin",
            type:'POST',

            success:function (data) {

            }
        });
    }
</script>

点击“前往管理员页面”按钮,在调试台可以看到如下 

这是因为框架内部防止CSRF(Cross-site request forgery跨站请求伪造)的发生,限制了除了get以外的大多数方法。

下面说解决办法: 
首先在标签内添加如下内容。

    <meta name="_csrf" th:content="${_csrf.token}"/>
    <meta name="_csrf_hader" th:content="${_csrf.headerName}"/>

只要添加这个token,后台就会验证这个token的正确性,如果正确,则接受post访问。 
然后在ajax代码中添加以下代码:

        var token = $('meta[name="_csrf"]').attr("content");
        var header = $('meta[name="_csrf_hader"]').attr("content");
        $(document).ajaxSend(function(e,xhr,opt){
            xhr.setRequestHeader(header,token);
        });

这样就可以正常使用POST、DELETE等其他方式来访问了。 
上面说到使用表单的POST方式来提交,通过查看页面源代码可以看到 

框架在form表单中自动插入了一个隐藏域,value值就是那个token,所以使用form表单来提交POST请求是可以直接通过的,而ajax方式提交的话,需要加上那段代码。

猜你喜欢

转载自blog.csdn.net/Heyll__/article/details/80067784