栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

Spring整合Spring Security OAuth2.0认证授权

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Spring整合Spring Security OAuth2.0认证授权

一、基本概念 1.1 什么是认证

比如:在初次使用微信前需要注册成为微信用户,然后输入账号和密码即可登录微信,输入账号和密码
登录微信的过程就是认证。

系统为什么要认证?
认证是为了保护系统的隐私数据与资源,用户的身份合法方可访问该系统的资源。

认证 :用户认证就是判断一个用户的身份是否合法的过程,用户去访问系统资源时系统要求验证用户的身份信
息,身份合法方可继续访问,不合法则拒绝访问。常见的用户身份认证方式有:用户名密码登录,二维码登录,手
机短信登录,指纹认证等方式。

1.2 什么是会话

用户认证通过后,为了避免用户重复认证,可将用户的信息保证在会话中。会话就是系统为了保持当前
用户的登录状态所提供的机制,常见的有基于session方式、基于token方式等。

基于session的认证方式如下图:
它的交互流程是,用户认证成功后,在服务端生成用户相关的数据保存在session(当前会话)中,发给客户端的
sesssion_id 存放到 cookie 中,这样用户客户端请求时带上 session_id 就可以验证服务器端是否存在 session 数
据,以此完成用户的合法校验,当用户退出系统或session过期销毁时,客户端的session_id也就无效了。

基于token方式如下图:
它的交互流程是,用户认证成功后,服务端生成一个token发给客户端,客户端可以放到 cookie 或 localStorage
等存储中,每次请求时带上 token,服务端收到token通过验证后即可确认用户身份。

基于session的认证方式由Servlet规范定制,服务端要存储session信息需要占用内存资源,客户端需要支持
cookie;基于token的方式则一般不需要服务端存储token,并且不限制客户端的存储方式,所以基于token的方式更适合。

1.3 什么是授权

授权: 授权是用户认证通过根据用户的权限来控制用户访问资源的过程,拥有资源的访问权限则正常访问,没有
权限则拒绝访问。

1.4 授权的数据模型

授权可简单理解为Who对What(which)进行How操作

Who,即主体(Subject),主体一般是指用户,也可以是程序,需要访问系统中的资源。
What,即资源(Resource),如系统菜单、页面、按钮、代码方法、系统商品信息、系统订单信息等。系统菜单、页面、按钮、代码方法都属于系统功能资源,对于web系统每个功能资源通常对应一个URL;系统商品信息、系统订单信息都属于实体资源(数据资源),实体资源由资源类型和资源实例组成,比如商品信息为资源类型,商品编号 为001的商品为资源实例。
How,权限/许可(Permission),规定了用户对资源的操作许可,权限离开资源没有意义,如用户查询权限、用户添加权限、某个代码方法的调用权限、编号为001的用户的修改权限等,通过权限可知用户对哪些资源都有哪些操作许可。

主体、资源、权限关系如下图:

主体、资源、权限相关的数据模型如下:
主体(用户id、账号、密码、…)
资源(资源id、资源名称、访问地址、…)
权限(权限id、权限标识、权限名称、资源id、…)
角色(角色id、角色名称、…)
角色和权限关系(角色id、权限id、…)
主体(用户)和角色关系(用户id、角色id、…)

主体(用户)、资源、权限关系如下图:

通常企业开发中将资源和权限表合并为一张权限表,如下:
资源(资源id、资源名称、访问地址、…)
权限(权限id、权限标识、权限名称、资源id、…)
合并为:
权限(权限id、权限标识、权限名称、资源名称、资源访问地址、…)

修改后数据模型之间的关系如下图:

1.5 RBAC

如何实现授权?业界通常基于RBAC实现授权。

1.5.1 基于角色的访问控制

RBAC基于角色的访问控制(Role-Based Access Control)是按角色进行授权,比如:主体的角色为总经理可以查
询企业运营报表,查询员工工资信息等,访问控制流程如下:

根据上图中的判断逻辑,授权代码可表示如下:

if(主体.hasRole("总经理角色id")){
查询工资
}

如果上图中查询工资所需要的角色变化为总经理和部门经理,此时就需要修改判断逻辑为“判断用户的角色是否是
总经理或部门经理”,修改代码如下:

if(主体.hasRole("总经理角色id") || 主体.hasRole("部门经理角色id")){
查询工资
}

根据上边的例子发现,当需要修改角色的权限时就需要修改授权的相关代码,系统可扩展性差。

1.5.2 基于资源的访问控制

RBAC基于资源的访问控制(Resource-Based Access Control)是按资源(或权限)进行授权,比如:用户必须
具有查询工资权限才可以查询员工工资信息等,访问控制流程如下:

根据上图中的判断,授权代码可以表示为:

if(主体.hasPermission("查询工资权限标识")){
查询工资
}

优点:系统设计时定义好查询工资的权限标识,即使查询工资所需要的角色变化为总经理和部门经理也不需要修改
授权代码,系统可扩展性强。

二、基于Session的认证方式 2.1 创建工程 2.1.1 新建maven工程,引入依赖


    4.0.0

    com.sun
    security-springmvc
    1.0-SNAPSHOT

    
    war
    
        UTF-8
        1.8
        1.8
    

    
        
            org.springframework
            spring-webmvc
            5.1.5.RELEASE
        

        
            javax.servlet
            javax.servlet-api
            3.0.1
            provided
        
        
            org.projectlombok
            lombok
            1.18.8
        
    
    
        security-springmvc
        
            
                
                    org.apache.tomcat.maven
                    tomcat7-maven-plugin
                    2.2
                
                
                    org.apache.maven.plugins
                    maven-compiler-plugin
                    
                        1.8
                        1.8
                    
                
                
                    org.apache.maven.plugins
                    maven-war-plugin
                    3.0.0
                    
                        false
                    
                
                
                    maven-resources-plugin
                    
                        utf-8
                        true
                        
                            
                                src/main/resources
                                true
                                
                                    ***.xml
                                
                            
                        
                    
                
            
        
    

2.1.2 Spring 容器配置
@Configuration
@ComponentScan(basePackages = "com.sun.security.springmvc"
        ,excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class ApplicationConfig {
    //在此配置除了Controller的其它bean,比如:数据库链接池、事务管理器、业务bean等。
}
2.1.3 servletContext配置
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.sun.security.springmvc"
        ,includeFilters= {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class WebConfig implements WebMvcConfigurer {

    //视频解析器
    @Bean
    public InternalResourceViewResolver viewResolver(){
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/view/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }
}
2.1.4 Spring容器初始化
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[]{WebConfig.class};
    }

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

2.3 实现认证功能 2.3.1 认证页面

在webapp/WEB-INF/views下定义认证页面login.jsp

<%@ page contentType="text/html;charset=UTF-8" pageEncoding="utf-8" %>


    用户登录


用户名:
密   码:

在WebConfig中新增如下配置,将/直接导向login.jsp页面:

 @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("login");
    }
2.3.2 认证接口

定义认证接口,此接口用于对传来的用户名、密码校验,若成功则返回该用户的详细信息,否则抛出错误异常:

public interface AuthenticationService {
    
    UserDto authentication(AuthenticationRequest authenticationRequest);
}

实现类

@Service
public class AuthenticationServiceImpl implements AuthenticationService {
    @Override
    public UserDto authentication(AuthenticationRequest authenticationRequest) {
        //校验参数是否为空
        if(null == authenticationRequest ||
                StringUtils.isEmpty(authenticationRequest.getUsername()) ||
                StringUtils.isEmpty(authenticationRequest.getPassword())){
            throw new RuntimeException("账号或密码为空");
        }
        //根据账号去查询数据库,这里测试程序采用模拟方法
        UserDto userDto = getUserDto(authenticationRequest.getUsername());
        if(null == userDto){
            throw new RuntimeException("查询不到该用户");
        }

        if(!userDto.getPassword().equals(authenticationRequest.getPassword())){
            throw new RuntimeException("账号或密码错误");
        }
        return userDto;
    }
    //模拟用户查询
    public UserDto getUserDto(String username){
        return userMap.get(username);
    }
    //用户信息
    private Map userMap = new HashMap<>();
    {
        userMap.put("zhangsan",new UserDto("1010","zhangsan","123","张三","133443"));
        userMap.put("lisi",new UserDto("1011","lisi","456","李四","144553"));
    }
}

请求头

@Data
@NoArgsConstructor
@AllArgsConstructor
public class AuthenticationRequest {
    //认证请求参数,账号、密码。。
    
    private String username;

    
    private String password;
}

返回值

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
    //用户身份信息
    private String id;
    private String username;
    private String password;
    private String fullname;
    private String mobile;
}

登录Controller

@RestController
public class LoginController {
    @Autowired
    AuthenticationService authenticationService;
    @RequestMapping(value = "/login",produces = "text/plain;charset=utf-8")
    public String login(AuthenticationRequest authenticationRequest){
        UserDto user = authenticationService.authentication(authenticationRequest);
        return user.getUsername()+"登录成功";
    }
}
2.4.实现会话功能

会话是指用户登入系统后,系统会记住该用户的登录状态,他可以在系统连续操作直到退出系统的过程。

(1)UserDto中定义一个SESSION_USER_KEY,作为Session中存放登录用户信息的key。

 public static final String SESSION_USER_KEY = "_user";

(2)修改LoginController,认证成功后,将用户信息放入当前会话。并增加用户登出方法,登出时将session置为失效。

//登录
    @PostMapping(value = "/login", produces = "text/plain;charset=utf-8")
    public String login(AuthenticationRequest authenticationRequest, HttpSession session){
        UserDto user = authenticationService.authentication(authenticationRequest);
        session.setAttribute(user.SESSION_USER_KEY,user);
        return user.getUsername()+"登录成功";
    }
    @GetMapping(value = "/logout",produces = {"text/plain;charset=UTF-8"})
    public String logout(HttpSession session){
        session.invalidate();
        return "退出成功";
    }

(3)从session中获取用户信息

 //从session中获取用户信息
    @GetMapping(value = "/session/getUser",produces = {"text/plain;charset=UTF-8"})
    public String getUser(HttpSession session){
        String fullname = null;
        Object object = session.getAttribute(UserDto.SESSION_USER_KEY);
        if(object == null){
            fullname = "匿名";
        }else{
            UserDto userDto = (UserDto) object;
            fullname = userDto.getFullname();
        }
        return fullname+"访问资源r1";
    }
2.5.实现授权功能

(1)为了实现这样的功能,我们需要在UserDto里增加权限属性,用于表示该登录用户所拥有的权限,同时修改UserDto的构造方法。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class UserDto {
    public static final String SESSION_USER_KEY = "_user";
    //用户身份信息
    private String id;
    private String username;
    private String password;
    private String fullname;
    private String mobile;
    
    private Set authorities;

}

(2)并在AuthenticationServiceImpl中为模拟用户初始化权限,其中张三给了p1权限,李四给了p2权限

//用户信息
    private Map userMap = new HashMap<>();
    {
        Set authorities1 = new HashSet<>();
        authorities1.add("p1");
        Set authorities2 = new HashSet<>();
        authorities2.add("p2");
        userMap.put("zhangsan",new UserDto("1010","zhangsan","123","张三","133443",authorities1));
        userMap.put("lisi",new UserDto("1011","lisi","456","李四","144553",authorities2));
    }

(3)在LoginController中增加测试资源2

  //从session中获取用户信息
    @GetMapping(value = "/session/getUser2",produces = {"text/plain;charset=UTF-8"})
    public String getUser2(HttpSession session){
        String fullname = null;
        Object object = session.getAttribute(UserDto.SESSION_USER_KEY);
        if(object == null){
            fullname = "匿名";
        }else{
            UserDto userDto = (UserDto) object;
            fullname = userDto.getFullname();
        }
        return fullname+"访问资源r2";
    }

(4)在interceptor包下定义SimpleAuthenticationInterceptor拦截器,实现授权拦截

1、校验用户是否登录
2、校验用户是否拥有操作权限

@Component
public class SimpleAuthenticationInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //在这个方法中校验用户请求的url是否在用户的权限范围内
        Object object = request.getSession().getAttribute(UserDto.SESSION_USER_KEY);
        if(null == object){
            //没有认证,提示登录
            writeContent(response,"请登录");
        }
        UserDto userDto = (UserDto) object;
        //请求的url
        String requestURI = request.getRequestURI();
        if(userDto.getAuthorities().contains("p1") && requestURI.contains("/session/getUser1")){
            return true;
        }
        if(userDto.getAuthorities().contains("p2") && requestURI.contains("/session/getUser2")){
            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();
    }
}

(5)在WebConfig中配置拦截器

	@Autowired
    SimpleAuthenticationInterceptor simpleAuthenticationInterceptor;
    
     @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(simpleAuthenticationInterceptor).addPathPatterns("/session
@Configuration
@ComponentScan(basePackages = "com.sun.security.springmvc"
        ,excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class ApplicationConfig {
    //在此配置除了Controller的其它bean,比如:数据库链接池、事务管理器、业务bean等。
}
3.1.3 Servlet Context配置
@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "com.sun.security.springmvc"
        ,includeFilters= {@ComponentScan.Filter(type = FilterType.ANNOTATION,value = Controller.class)})
public class WebConfig implements WebMvcConfigurer {
    //视频解析器
    @Bean
    public InternalResourceViewResolver viewResolver(){
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/WEB-INF/view/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }
    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("redirect:/login");
    }
}
3.1.4 初始化Spring容器
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[]{WebConfig.class};
    }

    //url-mapping
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}
3.2 认证 3.2.1 认证页面

springSecurity默认提供认证页面,不需要额外开发。

3.2.2 安全配置

spring security提供了用户名密码登录、退出、会话管理等认证功能,只需要配置即可使用。

  1. 在config包下定义WebSecurityConfig,安全配置的内容包括:用户信息、密码编码器、安全拦截机制。
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

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

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

    //安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/session/r1").hasAuthority("p1")
                .antMatchers("/session/r2").hasAuthority("p2")
                .antMatchers("/session
    @GetMapping(value = "/session/r1",produces = {"text/plain;charset=UTF-8"})
    public String r1(){
        return " 访问资源1";
    }

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

在安全配置类WebSecurityConfig.java中配置

//安全拦截机制(最重要)
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
                .antMatchers("/session/r1").hasAuthority("p1")
                .antMatchers("/session/r2").hasAuthority("p2")
                .antMatchers("/session/**").authenticated()//所有/session/**的请求必须认证通过
                .anyRequest().permitAll()//除了/session/**,其他请求可以访问
                .and()
                .formLogin()//运行表单登录
                .successForwardUrl("/login-success");//登录访问接口
    }
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/855197.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号