SpringSecurity应用

Security

需要的依赖

    <dependencies>
        <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-thymeleaf</artifactId>
        </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>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

配置

spring.security.user.password=123456
spring.security.user.name=tom
spring.security.user.roles=admin

当用户登陆系统时,会有一个角色.

入门

关于配置类的的自我定义

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置登录页面
        http.formLogin();
        //权限的配置
        http.authorizeRequests().anyRequest().fullyAuthenticated();
    }
}

​ 调用authorizeRequests()的意思是指通过authorizeRequests()方法来开始请求权限配置,anyRequest().fullyAuthenticated()是对http所有的请求必须通过授权认证才可以访问。除了fullyAuthenticated方法,还可以使用authenticated方法,这两个有什么区别呢?

​ 也就是说,fullyAuthenticated不仅可以让有权限的用户访问,还可以让remember-me的用户访问。所以,如果登录页面有记住我,一定要使用fullyAuthenticated。

关于自定义登录页面:

后端配置

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置登录页面
        http.formLogin().loginPage("/login");
        //权限的配置
        http.authorizeRequests().anyRequest().fullyAuthenticated();
    }
}

前端页面

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>自定义登录页</title>
</head>
<body>
<form action="/login" method="post">
    用户名: <input type="text" name="username" value="zhangsan" /> <br/>
    密码: <input type="password" name="password" value="123456" /> <br/>
    <input type="submit">
</form>
</body>
</html>

controller的拦截

@Controller
public class UserLoginController {
    @RequestMapping("/login")
    public String toFirstPage(){
        return "login";
    }
}

​ 1. 大体的运行流程是这样的,首先用户访问server端口(例如:localhost:8080),然后security自动跳转到"localhost:8080/login",即登录界面,然后我们把这个路径拦截下来导向我们自定义的登录界面即可,

这是登录界面的配置

​ 2.然后是自定义登录的一些权限配置.

​ 3.这里面有一个错误

在这里插入图片描述

解决方法:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //配置登录页面
        http.formLogin().loginPage("/login").permitAll();
        //权限的配置
        http.authorizeRequests().anyRequest().fullyAuthenticated();
    }
}

​ 原因是上面的权限配置已经说了:通过authorizeRequests()方法来开始请求权限配anyRequest().fullyAuthenticated()是对http所有的请求必须通过授权认证才可以访问。

除fullyAuthenticated方法.所以就出现登录界面的请求也需要验证下能登录,所以,加上

permitAll()让登录界面通过.进行登录

csrf的定义

第一种解决方法:隐藏参数的方式

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>自定义登录页</title>
</head>
<body>
<!--th:action="@{/login}"-->
<form  action="/login" method="post">
    用户名: <input type="text" name="username" value="zhangsan" /> <br/>
    密码: <input type="password" name="password" value="123456" /> <br/>
    <input th:name="${_csrf.parameterName}" type="hidden" th:value="${_csrf.token}" >
    <input type="submit">
</form>
</body>
</html>

第二种方式是springboot2.1新的解决办法,其实质也是添加了一个隐藏标签.

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>自定义登录页</title>
</head>
<body>
<form th:action="@{/login}" action="/login" method="post">
    用户名: <input type="text" name="username" value="zhangsan" /> <br/>
    密码: <input type="password" name="password" value="123456" /> <br/>
    <!--<input th:name="${_csrf.parameterName}" type="hidden" th:value="${_csrf.token}" >-->
    <input type="submit">
</form>
</body>
</html>

第三种方式后台直接关闭csrf

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //关闭csrf
        http.csrf().disable();
        //配置登录页面
        http.formLogin().loginPage("/login").permitAll();
        //权限的配置
        http.authorizeRequests().anyRequest().fullyAuthenticated();
    }
}

退出配置

其实security自带退出功能的

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>自定义登录页</title>
</head>
<body>
<h1>欢迎来到王者荣耀,敌军还有五秒到达战场!请做好准备.</h1>
<form th:action="@{/logout}" action="/login" method="post">
    <input type="submit" value="退出系统">
</form>
</body>
</html>

​ 当点击推出功能后,跳到登录界面,再访问/home界面,就不能访问了,需要用到最初的配置,任何url需要权限验证后登录.

​ 退出后,默认进入的自然是登录页面,但是浏览器路径上面,要显示出刚才是退出系统了。所以应该显示的路径是/login?logout,但是由于这个路径没有授权,会再次跳转到登录页面,显示的也就还是/login,所以我们对登出也要进行授权:

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //4.关闭csrf
        http.csrf().disable();
        //1.配置登录页面
        http.formLogin().loginPage("/login").permitAll();
        //3.登录成功后去的页面
       http.formLogin().successForwardUrl("/home");
        //5.退出
        http.logout().permitAll();
        //2.权限的配置
        http.authorizeRequests().anyRequest().fullyAuthenticated();
    }
}

退出结果

在这里插入图片描述

关于授权的内容

登录成功处理器:

​ 现在我们登录成功的时候直接跳转到了默认页面。有时候登录操作要求要记录一下日志再跳转,或者登录成功后执行一些其它逻辑再跳转,我们可以增加一个登录成功处理器LoginSuccessHandler,这个类需要实现 AuthenticationSuccessHandler 接口,并实现onAuthenticationSuccess方法

首先自定义一个LoginSucessHandler实现AuthenticationSuccessHandler接口

public class LoginSucesessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {

    }
    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {

    }
}

基于内存的认证

需要的类:web服务器上的权限适配器

WebSecurityConfigurerAdapter
@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception{
        authenticationManagerBuilder.inMemoryAuthentication()
                .withUser("admin").password("123456").roles("ADMIN","USER")
                .and()
                .withUser("sang").password("123456").roles("USER");
    }
}

security中的用户信息加密用的是BCryptPasswordEncoder这个类然后调用它的encode方法:具体实现如下

 @Test
    public void testBcryptPasswordEncoder(){
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
        String s = bCryptPasswordEncoder.encode("123456");
        System.out.println(s);
    }

输出的结果是:

$2a$10$ElhvfHTfM8tx/3RQKpJLj.v2xzoA5SGLGUcep2q1WjBk8HxMwu5iu

有时候需要获得用户信息然后在数据库中保存,所以这时候需要获得用户的信息,然后把用户信息存到数据库,或去用户信息的方式

HttpSecurity

配置好角色之后对资源要进行管理(即对访问路径的通过角色的控制),这是就要用到HttpSecurity来进行简单的自定义配置

@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception{
        authenticationManagerBuilder.inMemoryAuthentication()
                .withUser("root").password("123456").roles("ADMIN","DBA")
                .and()
                .withUser("admin").password("123456").roles("USER","ADMIN")
                .and()
                .withUser("tom").password("123456").roles("USER");
    }
    /**
     * 自定义资源管理
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
		//HttpSecuruty配置类的主要方法的使用
        http.authorizeRequests()
            //匹配路径,然后配置这个路径下那个角色能通过
                .antMatchers("/admin/**")
                .hasRole("ADMIN")
                .antMatchers("/user/**")
                .access("hasAnyRole('ADMIN','USER')")
                .antMatchers("/db/**")
                .access("hasAnyRole('ADMIN') and hasAnyRole('DBA')")
                .anyRequest()
                .authenticated()
                .and()
                .formLogin()
                .loginProcessingUrl("/login")
                .permitAll()
                .and()
                .csrf()
                .disable();

    }
}

1、CSRF是什么

​ CSRF(Cross Site Request Forgery),中文是跨站点请求伪造。CSRF攻击者在用户已经登录目标网站之后,诱使用户访问一个攻击页面,利用目标网站对用户的信任,以用户身份在攻击页面对目标网站发起伪造用户操作的请求,达到攻击目的。

​ spring有做这方面的措施是在filter中,我们使用即可.

登陆表单的详细配置

@Configuration
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception{
        authenticationManagerBuilder.inMemoryAuthentication()
                .withUser("root").password("123456").roles("ADMIN","DBA")
                .and()
                .withUser("admin").password("123456").roles("USER","ADMIN")
                .and()
                .withUser("tom").password("123456").roles("USER");
    }
    /**
     * 自定义资源管理
     * 一个and为一个设置的结束
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {

        http.authorizeRequests()
                /**
                 * 第一步设置路径访问权限
                 */
                .antMatchers("/admin/**")
                .hasRole("ADMIN")
                .antMatchers("/user/**")
                .access("hasAnyRole('ADMIN','USER')")
                .antMatchers("/db/**")
                .access("hasAnyRole('ADMIN') and hasAnyRole('DBA')")
                .anyRequest()
                .authenticated()
                .and()
//              开启表单登陆
                .formLogin()
//                默认表单登录访问的地址是login,即登陆请求处理接口,无论是移动端还是登陆界面都是这个口,上面配置了用户即身份角色密码
//                等信息,就是通过这个接口验证的
                .loginProcessingUrl("/login")
//                如果用户访问了一个未授权的网页就自动跳转到此页面,一般自定义即可,一般是登陆进去的首页页面
                .loginPage("/login_page")
                .usernameParameter("name")
                .passwordParameter("passwd")
//                登陆成功后的处理逻辑
                .successHandler(new AuthenticationSuccessHandler() {
                    @Override
                    public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse resp, Authentication auth) throws IOException, ServletException {
                        Object principal = auth.getPrincipal();
                        resp.setContentType("application/json;charset-utf-8");
                        PrintWriter writer = resp.getWriter();
                        resp.setStatus(200);
                        Map<String,Object> map = new ConcurrentHashMap<>();
                        map.put("status",200);
                        map.put("msg", principal);
                        ObjectMapper objectMapper = new ObjectMapper();
                        writer.write(objectMapper.writeValueAsString(map));
                        writer.flush();
                        writer.close();
                    }
                }).failureHandler(new AuthenticationFailureHandler(){

            @Override
            public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse resp, AuthenticationException e) throws IOException, ServletException {
                resp.setContentType("application/json;charset=utf-8");
                PrintWriter writer = resp.getWriter();
                resp.setStatus(401);
                Map<String,Object> map = new Hashtable<>();
                map.put("status",401);
                if(e instanceof LockedException){
                    map.put("msg","账户被锁定了");
                }else if(e instanceof BadCredentialsException){
                    map.put("msg","账户名或密码错误,请重新登陆");
                }else if(e instanceof DisabledException){
                    map.put("msg","账户被禁用,登陆失败");
                }else if(e instanceof AccountExpiredException){
                    map.put("msg","登陆已过期请重新登陆");
                }else if(e instanceof CredentialsExpiredException){
                    map.put("msg","密码已过期,请重新登陆!");
                }else {
                    map.put("msg","登陆失败!");
                }
                ObjectMapper objectMapper = new ObjectMapper();
                writer.write(objectMapper.writeValueAsString(map));
                writer.flush();
                writer.close();
            }
        })
                .permitAll()
                .and()
                .csrf()
                .disable();
    }
}

多个security的配置

对于业务比较复杂的,开发者需要配置多个HttpSecurity,实现对WebSecurityCofigureAdapter的多次扩展

@Configuration
public class MultiHttpSecurityConfig {
    @Bean
     PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }
    @Autowired
    protected void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception{
        authenticationManagerBuilder.inMemoryAuthentication()
                .withUser("admin").password("123456").roles("ADMIN","USER")
                .and()
                .withUser("john").password("123456").roles("USER");
    }

    @Configuration
    //优先级
    @Order(1)
    public static class AdminSecurityConfig extends WebSecurityConfigurerAdapter{
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.antMatcher("/admin/**").authorizeRequests()
                    .anyRequest().hasRole("ADMIN");
        }
    }
    @Configuration
    public static class OtherSecurityConnfig extends WebSecurityConfigurerAdapter{
        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                    .antMatchers("/login/**")
                    .access("hasAnyRole('ADMIN','USER')")
                    .antMatchers("/user/**")
                    .hasRole("USER")
//                    这两行的作用是除了上面定义的uri,用户访问其他uri都必须通过验证
                    .anyRequest()
                    .authenticated()
                    .and()
//                    一下两行是防止第三方恶意网站的攻击
                    .csrf()
                    .disable();
        }
    }
}

密码加密(对称加密md5)

关于md5的使用:

​ 为什么要对密码加密过再存到数据库,2011.11.21csdn有人把600多万个用户的信息放到了网上,并且是吧原生的没经过加密的原生的文件信息放到了数据库中,导致数据的公开,还有就是有的用户好多个系统公用一个密码,导致大量的个人信息的丢失,所以,了我们一般的做法是将敏感的数据加密后放到数据库,其中常用的是md5,对称加密.

​ 加密方案会用到散列函数:我们常用的是md5消息摘要算法,安全散列算法.

​ 只加密还是不够的,在加点料(salt:盐),如下BCryptPasswordEncoder底层实现:

    public String encode(CharSequence rawPassword) {
        String salt;
        if (this.random != null) {
            salt = BCrypt.gensalt(this.version.getVersion(), this.strength, this.random);
        } else {
            salt = BCrypt.gensalt(this.version.getVersion(), this.strength);
        }
        return BCrypt.hashpw(rawPassword.toString(), salt);
    }
//md5的加密方式
String md5Password = DigestUtils.md5Hex(password);

md5加密的实例:

@Override
    public TaotaoResult login(String username, String password, HttpServletRequest request, HttpServletResponse response) {
        /**
         * 验证账户密码,前端已经验证,这部分可以不写
         */
        if(StringUtils.isBlank(username) || StringUtils.isBlank(password)) {
            return TaotaoResult.build(500,"用户名或密码不能为空!");
        }
        try {
            //根据用户名在数据库查找用户
            TbUserExample condition = new TbUserExample();
            condition.createCriteria().andUsernameEqualTo(username);
            List<TbUser> userList = tbUserMapper.selectByExample(condition);
            if(userList == null || userList.size() < 1) {
                return TaotaoResult.build(500,"用户名或密码错误!");
            }
            TbUser userInfo = userList.get(0);
            //对用户的password密码加密
            String md5Password = DigestUtils.md5Hex(password);
            //数据库存的是md5对称加密后的数据
            if(!userInfo.getPassword().equals(md5Password)) {
                return TaotaoResult.build(500,"用户名或密码错误!");
            }
            // 生成用户Token(也可以使用JWT工具生成token,安全性更高
            String token = IDUtils.genImageNameByUUID();
            // 生成Redis Key
            String redisKey = getTokenRedisKey(token);
            // 存储到Redis之前,将用户密码清除。(可以降低安全风险)
            userInfo.setPassword(null);
            // 将用户信息存储到Redis
            redisTemplate.opsForValue().set(redisKey, JsonUtils.objectToJson(userInfo));
            // 设置Token过期时间
            redisTemplate.expire(redisKey, 30, TimeUnit.MINUTES);

            // 将Token写入Cookie
            CookieUtils.setCookie(request, response, "TT_TOKEN", token, (int)TimeUnit.MINUTES.toSeconds(30));

            return TaotaoResult.ok(token);
        } catch (Exception e) {
            log.error("登录失败", e);
            return TaotaoResult.build(500,"登录失败", ExceptionUtil.getStackTrace(e));
        }
    }

HttpClient使用注意事项:传的参数注意转换成json,默认的实体里面是String类型,没有自动转换,所以我们需要手东转换一下.接受的结果也是一样的,需要我们反序列化一下.

  @Override
    public TaotaoResult createService(Order order) {
        //调用订单系统服务提交订单
        CloseableHttpClient httpClient = HttpClients.createDefault();
        String resultStr = null;
        /**
         * ORDER_BASE_URL=http://localhost:8085
         * ORDER_CREATE_URL=/order/create
         */
        HttpPost httpPost = new HttpPost(ORDER_BASE_URL + ORDER_CREATE_URL);
        String orderJson = JsonUtils.objectToJson(order);
        try {
            httpPost.setEntity(new StringEntity(orderJson, ContentType.APPLICATION_JSON));
            CloseableHttpResponse resp = httpClient.execute(httpPost);
            HttpEntity entity = resp.getEntity();
            resultStr = EntityUtils.toString(entity);
        } catch (Exception e) {
            e.printStackTrace();
        }
        //转换成java对象
        TaotaoResult taotaoResult = TaotaoResult.format(resultStr);
        return taotaoResult;
    }

基于内存的简单小项目

首先创建maven项目

导入依赖

 <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.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

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

    </dependencies>

然后创建启动类

@SpringBootApplication
public class SecurityApplication {
    public static void main(String[] args) {

        SpringApplication.run(SecurityApplication.class,args);
    }
}

创建WebSecurityConfig用来配置用户基本信息和权限的信息

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Bean
    public PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }

    /**
     * 配置用户
     * @param auth
     * @throws Exception
     */

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin").password("123456").roles("ADMIN","DB","USER")
                .and()
                .withUser("tom").password("123456").roles("ADMIN","USER")
                .and()
                .withUser("john").password("123456").roles("USER");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /**
         * 用户请求权限设置
         */
        //拦截请求
        http.authorizeRequests()
                .antMatchers("/admin/**")
                .hasAnyRole("ADMIN")
                .antMatchers("/user/**")
                .access("hasAnyRole('ADMIN','USER')")
                .antMatchers("/db/**")
                .access("hasAnyRole('ADMIN')")
                .anyRequest()
                .fullyAuthenticated()
                .and()
                //登录界面的设置
                .formLogin().loginPage("/login")
                //默认登陆成功后去的路径
                .defaultSuccessUrl("/home",true)
                //登陆成功处理的handler
                .successHandler(new SucesessHandler())
               //允许通过登陆界面
                .permitAll()
                .and()
                .csrf()
                .disable();
    }
}

登陆成功后可以打一些日志或者其他处理的hangdler

public class SucesessHandler implements AuthenticationSuccessHandler {
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authentication) throws IOException, ServletException {
    }

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        System.out.println("恭喜你登陆成功!!!!!!!!!!!!!!");
        httpServletResponse.sendRedirect("/home");
    }
}

需要注意的时必须在第二个方法中写,在第一个方法中写无效

web层

@Controller
public class SecurityController {
    @RequestMapping("/login")
    public String toLogin(){
        return "login";
    }
    @RequestMapping("/home")
    public String toBome(){
        return "home";
    }
    @RequestMapping("db")
    public String toDb(){
        return "db";
    }
    @RequestMapping("/admin")
    public String toAdmin(){
        return "admin";
    }
    @RequestMapping("/user")
    public String toUser(){
        return "user";
    }
}

前端页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>管理员界面</title>
</head>
<body>
<h1>欢迎来到管理员界面</h1>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>数据库操作界面</title>
</head>
<body>

<h1>欢迎来到数据库操作界面</h1>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>首页</title>
</head>
<body>
<a href="/db">数据库</a>
<a href="/admin">管理员</a>
<a href="/user">普通用户</a>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>登录界面</title>
</head>
<body>
<form  method="post">
    用户名: <input type="text" name="username" value="zhangsan" /> <br/>
    密码: <input type="password" name="password" value="123456" /> <br/>
    <!--<input th:name="${_csrf.parameterName}" type="hidden" th:value="${_csrf.token}" >-->
    <input type="submit">
</form>
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>普通用户</title>
</head>
<body>
<h1>欢迎来到普通用户界面</h1>
</body>
</html>

基于方法的验证:

​ Web应用的安全管理,主要包括两个方面的内容,一个是用户身份的认证,即用户登录的设计,二是用户授权,即一个用户在一个应用系统中能够执行哪些操作的权限管理

​ 首先既然是基于方法的,那么我们就得知道他是用在service层的,即服务层,有时用在controller也行的只要是加在方法上就没得问题,具体测试做法如下

后端配置如下

@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true,securedEnabled = true,jsr250Enabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {


    @Bean
    public PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.inMemoryAuthentication()
                .withUser("admin").password("123456").roles("ADMIN","USER","DB")
                .and()
                .withUser("tom").password("123456").roles("ADMIN","USER")
                .and()
                .withUser("john").password("123456").roles("USER")
                .and()
                .withUser("jackson").password("123456").roles("DB");
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .fullyAuthenticated()
                .and()
                .formLogin().loginPage("/login")
                .defaultSuccessUrl("/home",true)
                .permitAll()
                .and()
                .csrf()
                .disable();
    }
}

然后需要在web层的具体代码和做法:验证不同用户对一个方法的调用

@Controller
public class UserController {
    @Autowired
    private StudentService studentService;
    @RequestMapping("/login")
    public String toLogin(){
        return "login";
    }
    @RequestMapping("home")
    public String toHome(){
        return "home";
    }
    @RequestMapping("/admin")
    public String toAdmin(){
        List<Student> students = studentService.findAllStudent();
        return "admin";
    }
    @RequestMapping("/db")
    public String toDb(){
        List<Student> students = studentService.findAllStudent();
        return "db";
    }
    @RequestMapping("/user")
    public String toUser(){
        List<Student> students = studentService.findAllStudent();
        return "user";
    }
}

service层

@Service
public class StudentServiceImpl implements StudentService {
    @Override
    @Secured("ROLE_ADMIN")
    public List<Student> findAllStudent() {
        CopyOnWriteArrayList<Student> students = new CopyOnWriteArrayList<>();
        Collections.addAll(students,new Student("张三","男",45),new Student("王五","男",36));
        return students;
    }
}

前端页面略,仿照上面的页面

测试结果:三个都能调用

首先admin超级管理员登上去
在这里插入图片描述

在这里插入图片描述

然后john登录上去

在这里插入图片描述
说明我们配置的没有问题

注意:配置类需要一个PasswordEncoder,这点容易忘.

基于JDBC得security(即基于数据库的security)

​ 前面我们定义用户是在配置文件和代码中定义死的默认用户,一般在开发中是不会这样做的,我们的用户都是来自我们的用户表,存储在数据库中。操作数据库的技术有很多,spring security默认支持了一个JDBC的方式,下面用这个方式来从数据库中查询用户

首先创建三张表:
在这里插入图片描述

表结构

role表结构

在这里插入图片描述

user表结构

在这里插入图片描述

user_role表结构

在这里插入图片描述

需要注意的是role表存name是要加ROLE_前缀

然后就是设计开口框架了

pojo

@Setter
@Getter
public class Role {
    private Integer id;

    private String name;

    private String namezh;
}
@Getter
@Setter
public class User implements UserDetails {
    private Integer id;
    private String username;
    private String password;
    private Boolean enable;
    private Boolean locked;
    private List<Role> roles;
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        List<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for(Role role : roles){
            authorities.add(new SimpleGrantedAuthority(role.getName()));
        }
        return authorities;
    }
    @Override
    public String getPassword() {
        return password;
    }
    @Override
    public String getUsername() {
        return username;
    }
    /**
     * 该账户是否未过期
     * @return
     */
    @Override
    public boolean isAccountNonExpired() {
        return true;
    }
    /**
     * 该账户是否未锁定
     * @return
     */
    @Override
    public boolean isAccountNonLocked() {
        return !locked;
    }

    /**
     * 该账户的密码是否未过期
     * @return
     */
    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }
    /**
     * 该账户是否可用
     * @return
     */
    @Override
    public boolean isEnabled() {
        return enable;
    }
}

mapper层

public interface UserMapper {
    User loadUserByUsername(String username);
    List<Role>  getUserRolesByUid(Integer id);
}

service层

@Service
public class UserService implements UserDetailsService {
    @Autowired
    private UserMapper userMapper;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userMapper.loadUserByUsername(username);
        if(user == null){
            throw new UsernameNotFoundException("账户不存在");
        }
        user.setRoles(userMapper.getUserRolesByUid(user.getId()));
        return user;
    }
}

配置

@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserService userService;
    @Bean
    PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userService);
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .anyRequest()
                .fullyAuthenticated()
                .and()
                .formLogin().loginPage("/login")
                .defaultSuccessUrl("/home",true)
                .permitAll()
                .and()
                .csrf()
                .disable();
    }
}

启动类

@SpringBootApplication
@MapperScan(basePackages = "com.security.lession.mapper")
public class SecurityApplication {
  /*
    @Autowired
    private StudentMapper studentMapper;

   @PostConstruct
    public void students(){
        List<Student> students = studentMapper.selectByExample(null);
        System.out.println(students);
    }*/

    public static void main(String[] args) {
        SpringApplication.run(SecurityApplication.class,args);
    }
}

还有就是角色继承

    @Bean
    public RoleHierarchy roleHierarchy(){
        RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
        String hierarchy = "ROLE_DBA > ROLE_ADMIN and ROLE_ADMIN > ROLE_USER";
        roleHierarchy.setHierarchy(hierarchy);
        return roleHierarchy;
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值