Spring Security认证与授权(session、security)

我们在做一个系统的时候,最不可少的就是登陆,然而登陆就涉及到认证(校验账号和密码)与授权(权限)。在此做一个小小总结

一、基于session的认证与授权

项目目录:

我们先看简单的一些层:

controller

package com.secrity.hello.controller;

import com.secrity.hello.enity.AuthenticationRequest;
import com.secrity.hello.enity.User;
import com.secrity.hello.service.AuthenticationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;

/**
 * @ClassName LoginController
 * @Description 验证用户登陆
 * @Author 
 * @Date 2020/5/3 12:22
 * @Version 1.0
 **/
@RestController
public class LoginController {

    @Autowired
    private AuthenticationService authenticationService;

    @PostMapping(value = "/login",produces = "text/plain;charset=utf-8")
    public String login(AuthenticationRequest request, HttpSession httpSession){

        User user = authenticationService.authentication(request);
        httpSession.setAttribute(User.SESSION_USER_KEY,user);
        return user.getNickname() + "登陆成功";
    }

    @GetMapping(value = "/loginout",produces = "text/plain;charset=utf-8")
    public String loginOut(HttpSession httpSession){
        httpSession.invalidate();
        return "退出成功";
    }

    @GetMapping(value = "/resources/r1",produces = "text/plain;charset=utf-8")
    public String res1(HttpSession session){
        String nickname = null;
        Object userObj = session.getAttribute(User.SESSION_USER_KEY);
        if(userObj != null){
            nickname =((User) userObj).getNickname();
        }
        return nickname +"访问资源r1";
    }

    @GetMapping(value = "/resources/r2",produces = "text/plain;charset=utf-8")
    public String res2(HttpSession session){
        String nickname = null;
        Object userObj = session.getAttribute(User.SESSION_USER_KEY);
        if(userObj != null){
            nickname =((User) userObj).getNickname();
        }
        return nickname +"访问资源r2";
    }

}

这里面一共有四个方法,第一个是登陆、第二个是退出。第三个和第四个是授权才能够访问的页面(拦截器拦截的页面)。这里的认证就是登陆,而授权就是看授予通过认证的用户是否有访问资源的权限。

service

package com.secrity.hello.service;

import com.secrity.hello.enity.AuthenticationRequest;
import com.secrity.hello.enity.User;

/**
 * @ClassName AuthenticationService  
 * @Description 用户认证,服务接口
 * @Author 
 * @Date 2020/5/3 12:11
 * @Version 1.0
 **/
public interface AuthenticationService  {

    /**
     * 用户认证
     * @param request 用户认证请求,账号和密码
     * @return 认证成功的用户信息
     */
    User authentication(AuthenticationRequest request);
}
package com.secrity.hello.service.Impl;

import com.secrity.hello.enity.AuthenticationRequest;
import com.secrity.hello.enity.User;
import com.secrity.hello.service.AuthenticationService;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * @ClassName AuthenticationServiceImpl
 * @Description 用户认证,服务接口实现类
 * @Author 
 * @Date 2020/5/3 12:21
 * @Version 1.0
 **/
@Service
public class AuthenticationServiceImpl implements AuthenticationService {

    //模拟数据库用户信息
    private Map<String,User> userMap = new HashMap<>();

    /**
     * 模拟查询数据库
     */
    {
        /**
         * 权限:1001 =》 r1,1002 =》 r2
         */
        Set<Integer> authorities1 = new HashSet<>();
        authorities1.add(1001);
        Set<Integer> authorities2 = new HashSet<>();
        authorities2.add(1002);
        userMap.put("zhangsan",new User(1l,"zhangsan","123","张三",authorities1));
        userMap.put("lisi",new User(2l,"lisi","456","李四",authorities2));
        userMap.put("wangwu",new User(3l,"wangwu","123","王五",null));
    }

    @Override
    public User authentication(AuthenticationRequest request) {
        if(request == null || StringUtils.isEmpty(request.getUsername()) || StringUtils.isEmpty(request.getPassword())){
            throw new RuntimeException("账号和密码为空");
        }
        User user = selectUser(request.getUsername());
        if(user == null){
            throw new RuntimeException("查询不到该用户");
        }
        if(!request.getPassword().equals(user.getPassword())){
            throw new RuntimeException("账号或密码错误");
        }
        return user;
    }

    public User selectUser(String username){
        return userMap.get(username);
    }


}

我们在这里没有调用数据库(我们的重点是明白认证与授权的过程)相信大家都能看得懂这个代码。

enity

package com.secrity.hello.enity;

import lombok.Data;

/**
 * @ClassName AuthenticationRequest
 * @Description 用户登陆请求类
 * @Author 
 * @Date 2020/5/3 12:19
 * @Version 1.0
 **/
@Data
public class AuthenticationRequest {

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

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

前端请求的参数,我们封装成了一个实体类。

package com.secrity.hello.enity;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.Set;

/**
 * @ClassName User
 * @Description 用户登陆实体类
 * @Author 
 * @Date 2020/5/3 12:12
 * @Version 1.0
 **/
@Data
@AllArgsConstructor
public class User {
    public static final String SESSION_USER_KEY = "_user";

    /**
     * 用户的信息
     */
    private Long id;
    private String username;
    private String password;
    private String nickname;

    /**
     * 权限
     */
    private Set<Integer> authorities;
}

interceptor

package com.secrity.hello.interceptor;

import com.secrity.hello.enity.User;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;

/**
 * @ClassName SimpleAuthenticationInterceptor
 * @Description 拦截器
 * @Author 
 * @Date 2020/5/3 12:58
 * @Version 1.0
 **/
@Component
public class SimpleAuthenticationInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        Object object = request.getSession().getAttribute(User.SESSION_USER_KEY);
        if(object == null){
            //没有认证,提示登录
            writeContent(response,"请登录");
        }
        User user = (User) object;
        String requestURI = request.getRequestURI();
        if( user.getAuthorities().contains(1001) && requestURI.contains("/resources/r1")){
            return true;
        }

        if( user.getAuthorities().contains(1002) && requestURI.contains("/resources/r2")){
            return true;
        }
        writeContent(response,"没有权限,拒绝访问");
        return false;
    }

    private void writeContent(HttpServletResponse response, String msg) throws IOException {
        response.setContentType("text/html;charset=utf-8");
        PrintWriter writer = response.getWriter();
        writer.print(msg);
        writer.close();
    }
}

别跟我说你不知道拦截器,哈哈哈哈。拦截器体现在前端控制器的doDispach方法。有兴趣的可以看看源代码。preHandle是在访问某一个controller之前进行拦截执行。具体的代码的逻辑细节不难。

config

package com.secrity.hello.config;

import com.secrity.hello.interceptor.SimpleAuthenticationInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.*;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

/**
 * @ClassName WebMvcConfig
 * @Description 相当于springmvc.xml文件
 * @Author 
 * @Date 2020/5/3 12:56
 * @Version 1.0
 **/
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.secrity.hello"
        ,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    private SimpleAuthenticationInterceptor simpleAuthenticationInterceptor;

    /**
     * 视图解析器
     */
    @Bean
    public InternalResourceViewResolver viewResolver(){
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/view/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

    /**
     * 配置自定义拦截器链
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(simpleAuthenticationInterceptor).addPathPatterns("/resources/**");
    }

    /**
     * 配置路径与映射
     */
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("login");
    }
}

这个配置类就相当于是相当于springmvc.xml文件。viewResolver方法是创建一个视图解析器。addInterceptors配置对应的资源路径的拦截。addViewControllers配置试图,就是你输入这个路径就访问到这个页面了。

package com.secrity.hello.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;

/**
 * @ClassName ApplicationConfig
 * @Description 相当于applicationContext.xml文件,在此配置除了Controller的其它bean,比如:数据库链接池、事务管理器、业务bean等
 * @Author 
 * @Date 2020/5/3 12:56
 * @Version 1.0
 **/
@Configuration
@ComponentScan(basePackages = "com.secrity.hello"
            ,excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class ApplicationConfig {

}

相当于applicationContext.xml文件,在此配置除了Controller的其它bean,比如:数据库链接池、事务管理器、业务bean等

init

package com.secrity.hello.init;

import com.secrity.hello.config.ApplicationConfig;
import com.secrity.hello.config.WebMvcConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/**
 * @ClassName SpringApplicationInitializer
 * @Description
 * @Author 
 * @Date 2020/5/3 12:54
 * @Version 1.0
 **/
public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    //spring容器,相当于加载 applicationContext.xml
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{ApplicationConfig.class};
    }

    //servletContext,相当于加载springmvc.xml
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebMvcConfig.class};
    }

    //url-mapping
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

加载这些配置文件。相当于一个自定义的前端控制器。

<%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" %>
<html>
<head>
    <title>用户登录login</title>
</head>
<body>
用户登录login
<form action="login" method="post">
    用户名:<input type="text" name="username"><br>
    密&nbsp;&nbsp;&nbsp;码:
    <input type="password" name="password"><br>
    <input type="submit" value="登录">
</form>
</body>
</html>

pom:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.secrity.hello</groupId>
    <artifactId>Security</artifactId>
    <version>1.0-SNAPSHOT</version>
    <modules>
        <module>session-web</module>
    </modules>
    <packaging>pom</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
    </dependencies>
</project>

 

二、基于security的认证与授权

这个要比以前老方法写的拦截器要简单一些

项目目录:

 

config

ApplicationConfig,这里跟之前上面的一样。我们看一下WebMvcConfig

package com.itheima.security.springmvc.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

/**
 * @ClassName WebMvcConfig
 * @Description 相当于SpringMvc.xml文件
 * @Author 
 * @Date 2020/5/3 12:56
 * @Version 1.0
 **/
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.itheima.security.springmvc"
        ,includeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class WebMvcConfig implements WebMvcConfigurer {


    //视频解析器
    @Bean
    public InternalResourceViewResolver viewResolver(){
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/view/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("redirect:/login");
    }

}

这里面没有了添加拦截器的那个方法,原因是我们的security已经提供给我们了拦截器。这个跳转login页面跟之前也不一样,使用的是框架自带的登陆页面。

package com.itheima.security.springmvc.config;

import org.springframework.context.annotation.Bean;
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.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.NoOpPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;

/**
 * @ClassName ApplicationConfig
 * @Description SpringSecurity的配置文件
 * @Author 
 * @Date 2020/5/3 12:56
 * @Version 1.0
 **/
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    //定义用户信息服务(查询用户信息)
    @Bean
    public UserDetailsService userDetailsService(){
        InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
        manager.createUser(User.withUsername("zhangsan").password("123").authorities("1001").build());
        manager.createUser(User.withUsername("lisi").password("456").authorities("1002").build());
        return manager;
    }

    //密码编码器
    @Bean
    public PasswordEncoder passwordEncoder(){
        return NoOpPasswordEncoder.getInstance();
    }

    //安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/r/r1").hasAuthority("1001")
                .antMatchers("/r/r2").hasAuthority("1002")
                .antMatchers("/r/**").authenticated()//所有/r/**的请求必须认证通过
                .anyRequest().permitAll()//除了/r/**,其它的请求可以访问
                .and()
                .formLogin()//允许表单登录
                .successForwardUrl("/login-success");//自定义登录成功的页面地址

    }
}

这个配置是我们的重点,userDetailsService是框架提供给我们的,我们这里使用的是框架带的类,并且也是在内存中的,如果查数据库是一样的道理。第二个方法是我们密码的一个编码加密校验方式,只要用户登陆就会帮我们去验证是否在这几个用户中,第三个方法是最重要的,配置了我们要对哪些url进行授权验证和认证成功之后跳转的登陆页面。

init 

package com.itheima.security.springmvc.init;

import com.itheima.security.springmvc.config.ApplicationConfig;
import com.itheima.security.springmvc.config.WebMvcConfig;
import com.itheima.security.springmvc.config.WebSecurityConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

/**
 * @ClassName SpringApplicationInitializer
 * @Description 相当于前端控制器
 * @Author 
 * @Date 2020/5/3 12:22
 * @Version 1.0
 **/
public class SpringApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    //spring容器,相当于加载 applicationContext.xml
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{ApplicationConfig.class, WebSecurityConfig.class};
    }

    //servletContext,相当于加载springmvc.xml
    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebMvcConfig.class};
    }

    //url-mapping
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

这里加了WebSecurityConfig的配置加载。

package com.itheima.security.springmvc.init;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

/**
 * @ClassName SpringSecurityApplicationInitializer
 * @Description 初始化security的类,若当前环境没有spring和springMVC,则super(WebSecurityConfig.class)加上
 * @Author 
 * @Date 2020/5/3 12:22
 * @Version 1.0
 **/
public class SpringSecurityApplicationInitializer
        extends AbstractSecurityWebApplicationInitializer {
    public SpringSecurityApplicationInitializer() {
        //super(WebSecurityConfig.class);
    }
}

这个配置是初始化Security的。

controller

package com.itheima.security.springmvc.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName LoginController
 * @Description 验证用户登陆
 * @Author 
 * @Date 2020/5/3 12:22
 * @Version 1.0
 **/
@RestController
public class LoginController {

    @RequestMapping(value = "/login-success",produces = {"text/plain;charset=UTF-8"})
    public String loginSuccess(){
        return " 登录成功";
    }

    /**
     * 测试资源1
     * @return
     */
    @GetMapping(value = "/r/r1",produces = {"text/plain;charset=UTF-8"})
    public String r1(){
        return " 访问资源1";
    }

    /**
     * 测试资源2
     * @return
     */
    @GetMapping(value = "/r/r2",produces = {"text/plain;charset=UTF-8"})
    public String r2(){
        return " 访问资源2";
    }
}

我们可以看到,用了Security框架之后我们的代码简化了很多。

pom:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.security.hello</groupId>
    <artifactId>security-spring-security</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-web</artifactId>
            <version>5.1.4.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-config</artifactId>
            <version>5.1.4.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.0.1</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>
    </dependencies>
    <build>
        <finalName>security-springmvc</finalName>
        <pluginManagement>
            <plugins>
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.2</version>
                </plugin>
                <plugin>
                    <groupId>org.apache.maven.plugins</groupId>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.8</source>
                        <target>1.8</target>
                    </configuration>
                </plugin>

                <plugin>
                    <artifactId>maven-resources-plugin</artifactId>
                    <configuration>
                        <encoding>utf-8</encoding>
                        <useDefaultDelimiters>true</useDefaultDelimiters>
                        <resources>
                            <resource>
                                <directory>src/main/resources</directory>
                                <filtering>true</filtering>
                                <includes>
                                    <include>**/*</include>
                                </includes>
                            </resource>
                            <resource>
                                <directory>src/main/java</directory>
                                <includes>
                                    <include>**/*.xml</include>
                                </includes>
                            </resource>
                        </resources>
                    </configuration>
                </plugin>
            </plugins>
        </pluginManagement>
    </build>

</project>

 

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值